Use a class field in a lambda

I don't understand this behavior.

This piece of code corresponds to:

public class A {

    private String s;

    private Function<String, String> f = e -> s;

    public A(String s) {
        this.s = s;
    }
}

      

But if I do s

final then I get compiler error:

public class A {

    private final String s;

    private Function<String, String> f = e -> s; // Variable 's' might not have been initialized

    public A(String s) {
        this.s = s;
    }
}

      

Why? If it were the other way around, I would understand, but how does the compiler complain when I declare a field final

(which forces me to initialize its value in the constructor), and is it okay for it to fail final

?

+3


source to share


3 answers


This has nothing to do with lambda, this example has the same error:

public class Test {
    private final String a;
    private String b = a; // // Variable 'a' might not have been initialized

    public Test(String a) {
        this.a = a;
    }
}

      

This is because the initialization at the declaration site is done before the constructor. Therefore, in place of the declaration b

, a

it is still not initialized.

It's clear if you use this example:

public class Test {
    private String a = "init";
    private String b = a;

    public Test(String a) {
        this.a = a;
    }

    public static void main(String[] args) {
        System.out.println(new Test("constructor").b);
    }
}

      



When you run it, it prints "init"

(the value the field was originally assigned to a

), not "constructor"

since the initialization b

took place before running the constructor.

The lambda from your example makes it more confusing, because we might expect that since access is a

deferred, everything will be fine with the compiler, but apparently the compiler just follows the general rule of "don't access a variable before initializing it".

You can get around it with an accessor method:

public class Test {
    private final String a;
    private String b = getA(); // allowed now, but not very useful
    private Function<String, String> f = e -> getA(); // allowed now and evaluated at the time of execution of the function

    public Test(String a) {
        this.a = a;
    }

    public static void main(String[] args) {
        System.out.println(new Test("constructor").b); // prints "null"
        System.out.println(new Test("constructor").f.apply("")); // prints "constructor"
    }

    public String getA() {
        return a;
    }
}

      

+11


source


A non-final member variable will always be initialized (since it has a default value - null

in the case of your variable String

), so there is no way to uninitialize it.

On the other hand, the final variable can only be initialized once, so my guess is that it is not initialized with a default value.

The closest related thing I've found is in JLS 4.12.4. :

4.12.4. final variables

A variable can be declared final. The final variable can only be assigned once. It is a compile-time error if a final variable is assigned unless it is definitely unassigned just before the assignment

I assume we can understand this last sentence to mean that the final variable is not assigned a default value, as otherwise you will get a compile-time error in this.s = s;

.

Best JLS link (thanks Holger) JLS 16 :



Chapter 16. Specific purpose

For every access to a local variable or an empty trailing field x, it must be specified before access or a compile-time error .

The rational for this requirement is that (in your example) the lambda expression can be called before initialization s

:

public A(String s) {
    String v = f.apply("x"); // this.s is not initialized at this point
                             // so it can't be accessed
    this.s = s;
}

      

Note that you can initialize the lambda expression in the constructor after initializing the final variable (I changed the argument name to a variable than a member variable so that the lambda expression will not capture that local variable):

public A(String so) {
    // f = e -> s; // Error: The blank final field s may not have been initialized
    this.s = so;
    f = e -> s; // works fine
}

      

+5


source


This is also a possible way to do

 public class A {

   private final String s;
   private Function<String, String> f;

   public A(String s) {
    this.s = s;
    this.f = e -> s;
   }
}

      

0


source







All Articles