Lambda expression using a final field in object initialization block

Remi Forax forax at univ-mlv.fr
Mon Mar 4 12:37:53 UTC 2019


Hi Jaikiran,
the initialisation block is called before the constructor so at that point name is not initialised, that why your first code fails.
For the second one, 
  this.suppliers.put("a", new Supplier<String>() {
             @Override
             public String get() {
                 return name;
             }
  });

  "return name" here is equivalent to return LambdaTest.this.name, with LambdaTest.this being the same reference as this inside the initialization block,
  at that point the compiler doesn't track if name is initialized or not anymore, that's why it compiles. 

so you can fool the compiler the same way with a lambda by introducing a local variable, i.e the example below should compile
    {
        var that = this;
        this.suppliers.put("a", () -> that.name);
    } 

Note, this mailing list is a list to discuss how to implement the JDK not how to use it.
This kind of question will be answered better and faster on stackoverflow.com.

regards,
Rémi

----- Mail original -----
> De: "Jaikiran Pai" <jai.forums2013 at gmail.com>
> À: "jdk-dev" <jdk-dev at openjdk.java.net>
> Envoyé: Lundi 4 Mars 2019 10:56:28
> Objet: Lambda expression using a final field in object initialization block

> Please consider this trivial code:
> 
> 
> import java.util.Map;
> import java.util.HashMap;
> import java.util.function.Supplier;
> 
> public class LambdaTest {
>    private final String name;
> 
>    private final Map<String, Supplier<String>> suppliers = new HashMap<>();
> 
>    {
>        this.suppliers.put("a", () -> name);
>    }
> 
>    private LambdaTest(final String name) {
>        this.name = name;
>    }
> 
>    public static void main(final String[] args) throws Exception {
>        final LambdaTest self = new LambdaTest("hello");
>    }
> }
> 
> When I compile this with Java 8 (javac 1.8.0_172) and even Java 11
> (javac 11.0.1), I get a compilation error, in the object initialization
> block where I am using a lambda expression to populate the "suppliers"
> Map and am referring to a final field (called "name") of the class. The
> compilation error is:
> 
> LambdaTest.java:12: error: variable name might not have been initialized
>        this.suppliers.put("a", () -> name);
>                                      ^
> 1 error
> 
> Now, if I replace that piece of code with:
> 
> 
> import java.util.Map;
> import java.util.HashMap;
> import java.util.function.Supplier;
> 
> public class LambdaTest {
>    private final String name;
> 
>    private final Map<String, Supplier<String>> suppliers = new HashMap<>();
> 
>    {
>        this.suppliers.put("a", new Supplier<String>() {
>            @Override
>            public String get() {
>                return name;
>            }
>        });
>    }
> 
>    private LambdaTest(final String name) {
>        this.name = name;
>    }
> 
>    public static void main(final String[] args) throws Exception {
>        final LambdaTest self = new LambdaTest("hello");
>    }
> }
> 
> The only change in this is the object initialization block, where I no
> longer use a lambda and instead use an anonymous class. This passes
> compilation successfully.
> 
> Why is it an error to use such a final field in a lambda expression? The
> (basic) documentation[1] of Lambda expressions doesn't seem to mention
> this construct as invalid.
> 
> 
> [1]
> https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
> 
> -Jaikiran


More information about the jdk-dev mailing list