Lambda expression using a final field in object initialization block

Jaikiran Pai jai.forums2013 at gmail.com
Sat Mar 9 13:29:14 UTC 2019


Hello Remi,

Thank you for that detailed explanation. That was helpful!

-Jaikiran

On 06/03/19 6:01 PM, forax at univ-mlv.fr wrote:
> ----- Mail original -----
>> De: "Jaikiran Pai" <jai.forums2013 at gmail.com>
>> À: "Remi Forax" <forax at univ-mlv.fr>
>> Cc: "jdk-dev" <jdk-dev at openjdk.java.net>
>> Envoyé: Mercredi 6 Mars 2019 07:38:24
>> Objet: Re: Lambda expression using a final field in object initialization block
>> Hello Remi,
>>
>> Thank you for responding :) The reason why I asked this in the jdk-dev
>> mailing list was a combination of 2 things:
>>
>> 1. I didn't understand why the compiler would want the value to be
>> initialized at all at that place in the lambda usage and the docs around
>> this wasn't clear either. I still don't understand why it needs that
>> field to be initialized whereas its equivalent anonymous class
>> counterpart doesn't need it to be initialized. With my limited
>> understanding of lambdas I was under the impression that the members
>> used in the lambda expression will only be evaluated when the lambda is
>> invoked, by which time, in this specific case the field will be
>> initialized since it's a final member of the instance.
> ok, first, you have to understand that the uninitialised fields detection in constructors is here to avoid users to routinely shoot themselves into the foot, it's like a fence near a cliff.
> It relies on the fact that the compiler can see if the code use a field before the field being initialized but obviously we know that for some codes the compiler has no way to see if the field is really initialized or not.
> Now, there are two kinds of lambdas, lambdas that are delayed computations and lambdas that abstract a block of code (as a function abstract a block of code), the former are executed at some point in time, the later are executed directly. In some languages you have two different syntax, block and Proc in Ruby, in Kotlin, you tag the function that receive a lambda with inline if you want the lambda to act like a block of code. In Java, there is no way to tell the difference but we want to stay open to make a difference between the two use cases in the future.
>
> So given that detecting uninitialised fields is a user protection mechanism and that in the future, we may make a difference between the two kind of lambdas, the lambda expert group has decided that the compiler will consider the worst case, i.e. that lambdas can be executed just after being created.
>
> Here is an example of lambdas used as block of code executed directly
>   class Foo {
>     final String name;
>  
>     Foo(List<String> list) {
>       list.removeIf(s -> s.equals(name));
>       name = ...
>     }
>   }  
>
>
>> 2. The mailing list index[1] mentioned this as the place for "Technical
>> discussion related to the JDK Project".
>>
>> Having said that, I do respect and understand what you mean by this
>> being a user/usage question and I'll pursue this further in a different
>> forum.
>>
>> [1] https://mail.openjdk.java.net/mailman/listinfo
>>
>> -Jaikiran
> Rémi
>
>> On 04/03/19 6:07 PM, Remi Forax wrote:
>>> 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