Why do lambda and anonymous class have different implicitly added fields?

Viewed 290

It is well-known that both anonymous inner classes and capturing lambdas have implicitly added private fields in some cases. For instance-field anonymous class it is a reference to the outer class instance and for local and static anonymous instances nothing. But local-instance lambda differs: it has a field of captured variable's type. If in the end they are used the same way, why do they have this difference? Why cannot local lambda be built in the exact copy of local anonymous class?

UPD:

A little sample

Class to test anonymous class's fields

public class AnonymousExample {

    private Runnable instanceRunnable = new Runnable() {
        @Override
        public void run() { }
    };

    private static Runnable staticRunnable = new Runnable() {
        @Override
        public void run() { }
    };

    public static void main(String[] args) throws IllegalAccessException {
        Runnable localRunnable = new Runnable() {
            @Override
            public void run() { }
        };
        System.out.println("-- local anonymous class --");
        print(localRunnable.getClass(), localRunnable);

        System.out.println("\n-- instance anonymous class --");
        AnonymousExample em = new AnonymousExample();
        print(em.instanceRunnable.getClass(), em.instanceRunnable);

        System.out.println("\n-- static member anonymous class --");
        print(staticRunnable.getClass(), staticRunnable);
    }

    public static void print(Class<?> c, Object instance) throws IllegalAccessException {
        System.out.println("- constructors -");
        for (Constructor<?> constructor : c.getDeclaredConstructors()) {
            System.out.println(constructor.toGenericString());
        }
        System.out.println("- fields -");
        for (Field field : c.getDeclaredFields()) {
            System.out.printf(" field name: %s,%n field type: %s,%n field value: %s%n",
                    field.getName(),
                    field.getType(),
                    field.get(instance));
        }
    }

}

Output:

-- local anonymous class --
- constructors -
field.AnonymousExample$3()
- fields -

-- instance anonymous class --
- constructors -
field.AnonymousExample$1(field.AnonymousExample)
- fields -
 field name: this$0,
 field type: class field.AnonymousExample,
 field value: field.AnonymousExample@79fc0f2f

-- static member anonymous class --
- constructors -
field.AnonymousExample$2()
- fields -

Class to test capturing lambda's fields

    public class CapturingLambdaExample {

    private int x = 10;
    private static int y = 20;

    private Runnable instanceRunnable = () -> { System.out.println(x);};
    private static Runnable staticRunnable = () -> { System.out.println(y);};

    public static void main(String[] args) throws IllegalAccessException {
        int z = 20;//effectively final
        Runnable localRunnable = () -> { System.out.println(z);};
        System.out.println("-- local capturing lambda --");
        print(localRunnable.getClass());

        System.out.println("\n-- instance capturing lambda --");
        CapturingLambdaExample em = new CapturingLambdaExample();
        print(em.instanceRunnable.getClass());

        System.out.println("\n-- static capturing lambda --");
        print(staticRunnable.getClass());
    }

    public static void print(Class<?> c) throws IllegalAccessException {
        System.out.println("- constructors -");
        for (Constructor<?> constructor : c.getDeclaredConstructors()) {
            System.out.println(constructor.toGenericString());
        }
        System.out.println("- fields -");
        for (Field field : c.getDeclaredFields()) {
            System.out.printf(" field name: %s,%n field type: %s,%n ",
                    field.getName(),
                    field.getType());
        }
    }

}

Output:

-- local capturing lambda --
- constructors -
private field.CapturingLambdaExample$$Lambda$15/0x0000000840065440(int)
- fields -
 field name: arg$1,
 field type: int,

-- instance capturing lambda --
- constructors -
private field.CapturingLambdaExample$$Lambda$25/0x0000000840068840(field.CapturingLambdaExample)
- fields -
 field name: arg$1,
 field type: class field.CapturingLambdaExample,

-- static capturing lambda --
- constructors -
private field.CapturingLambdaExample$$Lambda$14/0x0000000840064c40()
- fields -
1 Answers

Anonymous class and Lambda do have some differences.

  1. Anonymous class may extend another class and also can have more than one method. So the keywords, this and super need to be used to access its own members and its super class members respectively. Also, if an anonymous class needs to access the enclosing class & the enclosing class's parent members it has to use qualified this and super keywords. Example: EnclosingClass.this.toString()

  2. On the other hand, Lambda can only implement an interface with one method. It has no other members, so the keywords, this and super can rather be used to refer to the Enclosing class and its parent's members.

Quoting from Java Spec

Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context

Example Code:

public class EnclosingClass {

    public static void main(String[] args) {
        new EnclosingClass().start();
    }

    private void run(Runnable runnable) {
        runnable.run();
    }

    private void start() {
        System.out.println("--- Lambda ---");
        run(() -> {
            System.out.println(this.toString());
            System.out.println(super.toString()); // Use Object.toString
        });

        System.out.println("---Anonymous class---");
        run(new AnonymousSuperClass() {
            @Override
            public void run() {
                super.commonMethod();
                System.out.println(this.toString());
                System.out.println(super.toString());
                System.out.println(EnclosingClass.this.toString());
                System.out.println(EnclosingClass.super.toString()); // Use Object.toString
            }

            @Override
            public String toString() {
                return "Anonymous Class";
            }
        });
    }

    @Override
    public String toString() {
        return "Enclosing Class";
    }

    private static abstract class AnonymousSuperClass implements Runnable {
        protected void commonMethod() {
            System.out.println("Some common processing");
        }

        @Override
        public String toString() {
            return "Anonymous Super Class";
        }
    }
}

Sample Output:

--- Lambda ---
Enclosing Class
EnclosingClass@eed1f14
---Anonymous class---
Some common processing
Anonymous Class
Anonymous Super Class
Enclosing Class
EnclosingClass@eed1f14

Why local capturing lambda needs implicitly added field when anonymous class don't? This is not true. In your example, anonymous class was not using any field from the enclosing method.

For the following snippet,

private static void start() throws IllegalAccessException {
        int x = 5;
        System.out.println("---- Anonymous class ----");
        print(new Runnable() {

            @Override
            public void run() {
                System.out.println(x);
            }
        }.getClass());

        System.out.println("---- Lambda class ----");
        Runnable r = () -> System.out.println(x);
        print(r.getClass());
    }

This is the output:

---- Anonymous class ----
- constructors -
EnclosingClassV2$1(int)
- fields -
 field name: val$x,
 field type: int,
 ---- Lambda class ----
- constructors -
private EnclosingClassV2$$Lambda$1/455659002(int)
- fields -
 field name: arg$1,
 field type: int,

Note:

  1. Since, anonymous instance is present inside the static method, no reference to the enclosing instance is available.
  2. The access specifier for anonymous class is default while for lambda it is private. I could not see any reference in the spec related to this. I dont see any reason why the access specifier for anonymous class cannot be private. May be since, anonymous class is an extension of inner class, the same constructor generation strategy has been reused.

To conclude, a different strategy is followed for generating lambda classes, so that the additional class generation complexity involved in anonymous class can be avoided.

Related