Search code examples
lambdajava-8final

Lambdas accessing final fields of the enclosing class before initialisation


I found behaviour which I cannot explain when accessing a final field of a class from an enclosed lambda expression. This is the example program:

public class FinalAccessTest {

  // Some fields
  private int nonfinalfield=4;
  private final int directfinalfield=5;
  private final int constructorfinalfield;

  // element method for the final field with delayed assignment
  private int getConstructorFinalField() { return constructorfinalfield; }

  // Some lambda expressions accessing the fields from above
  private final Supplier<Integer> nonfinalsupp=()->nonfinalfield;  // OK
  private final Supplier<Integer> directfinalsupp=()->directfinalfield;  // OK
  private final Supplier<Integer> constructorfinalsuppA=
    ()->this.constructorfinalfield;  // OK
  private final Supplier<Integer> constructorfinalsuppB=
    ()->getConstructorFinalField();   // OK

  // This one does not compile: The blank final field
  //   constructorfinalfield may not have been initialized
  //private Supplier<Integer> constructorfinalsuppC=()->constructorfinalfield;

  public FinalAccessTest() {
    System.out.println(constructorfinalsuppA.get()); // prints "0"
    System.out.println(constructorfinalsuppB.get()); // prints "0"

    // Does not compile: "may not have been initialized"
    //System.out.println(constructorfinalfield);

    constructorfinalfield=6;
    System.out.println(constructorfinalsuppA.get()); // prints "6"
    System.out.println(constructorfinalsuppB.get()); // prints "6"
    System.out.println(constructorfinalfield); // Compiles and prints "6"

    // now access to a non-final field
    System.out.println(nonfinalsupp.get()); // prints "4"
    nonfinalfield=17;
    System.out.println(nonfinalsupp.get()); // prints "17"

  }

  public static void main(String[] args) { new FinalAccessTest(); }

}

Obviously it is possible to access a final field of a class which is initialized within the constructor before its actual initialisation in a lambda expression by

  • either referring to the field by this.<field>
  • or using an element method get<Field>().

On the other hand, it seems generally not possible to access such final fields by simply referencing <field> in the lambda expression. And I thought, this in a lambda always referencing the enclosing class so that <field> and this.<field> are effectively the same.

To make confusion complete, this all only applies to final fields which are initialized in the constructor. For non-final fields or final fields which are initialized in the declaration, access by <field> is perfectly possible and uses the value of the field at lambda invocation time.

What is going on here? Is this a general shortcoming of the lambda expression definition/engine, a bug in specific Java versions (8u66) or just a feature? I'm a bit puzzled. What am I getting wrong?


Solution

  • Object initializers (i.e. declarations of the form field = value in your class) run before the constructor in order of appearance. Thus, the first declarations all access fields which have already been initialized at that time, while the fields created in the constructor will only be initialized after the respective object initializer.