25

I've recently been encountering the error message "The blank final field obj may not have been initialized".

Usually this is the case if you try to refer to a field that is possibly not assigned to a value yet. Example class:

public class Foo { private final Object obj; public Foo() { obj.toString(); // error (1) obj = new Object(); obj.toString(); // just fine (2) } } 

I use Eclipse. In the line (1) I get the error, in the line (2) everything works. So far that makes sense.

Next I try to access obj within an anonymous interface I create inside the constructor.

public class Foo { private Object obj; public Foo() { Runnable run = new Runnable() { public void run() { obj.toString(); // works fine } }; obj = new Object(); obj.toString(); // works too } } 

This works, too, since I do not access obj in the moment I create the interface. I could also pass my instance to somewhere else, then initialize the object obj and then run my interface. (However it would be appropriate to check for null before using it). Still makes sense.

But now I shorten the creation of my Runnable instance to the burger-arrow version by using a lambda expression:

public class Foo { private final Object obj; public Foo() { Runnable run = () -> { obj.toString(); // error }; obj = new Object(); obj.toString(); // works again } } 

And here is where I can't follow anymore. Here I get the warning again. I am aware that the compiler doesn't handle lambda expressions as usual initializations, it doesn't "replace it by the long version". However, why does this affect the fact that I do not run the code part in my run() method at creation time of the Runnable object? I am still able to do the initialization before I invoke run(). So technically it is possible not to encounter a NullPointerException here. (Though it would be better to check for null here, too. But this convention is another topic.)

What is the mistake I make? What is handled so differently about lambda that it influences my object usage the way it does?

I thank you for any further explanations.

3
  • 1
    What warning/error do you get with the final version? What version of Java are you using? Commented May 20, 2015 at 21:42
  • 1
    @SotiriosDelimanolis I get the exact error as mentioned in the title. And I use Java 8. I think otherwise I wouldn't even be able to use lambda, right? Commented May 20, 2015 at 22:12
  • 1
    Sorry, I meant the compiler version. It's java 8, but which release version. Commented May 20, 2015 at 22:13

4 Answers 4

17

You can bypass the problem by

 Runnable run = () -> { (this).obj.toString(); }; 

This was discussed during lambda development, basically the lambda body is treated as local code during definite assignment analysis.

Quoting Dan Smith, spec tzar, https://bugs.openjdk.java.net/browse/JDK-8024809

The rules carve out two exceptions: ... ii) a use from inside of an anonymous class is okay. There is no exception for a use inside of a lambda expression

Frankly I and some other people thought the decision is wrong. The lambda only captures this, not obj. This case should have been treated the same as anonymous class. The current behavior is problematic for many legit use cases . Well, you can always bypass it using the trick above- fortunately definite assignment analysis is not too smart and we can fool it.

Sign up to request clarification or add additional context in comments.

12 Comments

I don’t think that the technical aspects of the compilation should drive the formal definitions of the specification. Technically, lambdas, as compiled today, capture this to access fields, even final fields, but that might change in future versions. And the flow analysis does not prevent the Runnable’s run method to be invoked before obj has been initialized. And lambdas should not behave like inner classes as they are not inner classes. They don’t behave like them in many aspects and making them behave like them in this aspect would be a step backwards.
capturing-this-only is in the spec, and I don't think they can change that in future versions.
Can you provide a link to the relevant part of the spec? I didn’t find anything about such a behavior in the specification.
@Holger - dude, I should - but I really don't want to dive in the spec again; it's a beast.
ahem and what is this question all about? It’s about the fact, that for an expression consisting of a simple name or a simple name qualified with this (without braces), the compiler does enforce this rule. Hence for these kinds of access, the field doesn’t need to be accessed via repeated reads. The rest is given by the Memory Model: “… compilers are allowed to keep the value of a final field cached in a register and not reload it from memory in situations where a non-final field would have to be reloaded
|
11

I can't reproduce the error for your final case with Eclipse's compiler.

However, the reasoning for the Oracle compiler I can imagine is the following: inside a lambda, the value of obj must be captured at declaration time. That is, it must be initialized when it is declared inside the lambda body.

But, in this case, Java should capture the value of the Foo instance rather than obj. It can then access obj through the (initialized) Foo object reference and invoke its method. This is how the Eclipse compiler compiles your piece of code.

This is hinted at in the specification, here:

The timing of method reference expression evaluation is more complex than that of lambda expressions (§15.27.4). When a method reference expression has an expression (rather than a type) preceding the :: separator, that subexpression is evaluated immediately. The result of evaluation is stored until the method of the corresponding functional interface type is invoked; at that point, the result is used as the target reference for the invocation. This means the expression preceding the :: separator is evaluated only when the program encounters the method reference expression, and is not re-evaluated on subsequent invocations on the functional interface type.

A similar thing happens for

Object obj = new Object(); // imagine some local variable Runnable run = () -> { obj.toString(); }; 

Imagine obj is a local variable, when the lambda expression code is executed, obj is evaluated and produces a reference. This reference is stored in a field in the Runnable instance created. When run.run() is called, the instance uses the reference value stored.

This cannot happen if obj isn't initialized. For example

Object obj; // imagine some local variable Runnable run = () -> { obj.toString(); // error }; 

The lambda cannot capture the value of obj, because it doesn't have a value yet. It's effectively equivalent to

final Object anonymous = obj; // won't work if obj isn't initialized Runnable run = new AnonymousRunnable(anonymous); ... class AnonymousRunnable implements Runnable { public AnonymousRunnable(Object val) { this.someHiddenRef = val; } private final Object someHiddenRef; public void run() { someHiddenRef.toString(); } } 

This is how the Oracle compiler is currently behaving for your snippet.

However, the Eclipse compiler is, instead, not capturing the value of obj, it's capturing the value of this (the Foo instance). It's effectively equivalent to

final Foo anonymous = Foo.this; // you're in the Foo constructor so this is valid reference to a Foo instance Runnable run = new AnonymousRunnable(anonymous); ... class AnonymousRunnable implements Runnable { public AnonymousRunnable(Foo foo) { this.someHiddenRef = foo; } private final Foo someHiddenFoo; public void run() { someHiddenFoo.obj.toString(); } } 

Which is fine because you assume that the Foo instance is completely initialized by the time run is invoked.

18 Comments

@SteffenT Version: Luna Release (4.4.0) Build id: 20140612-0600
@SteffenT I tried to find a JLS section that explains the capture logic but I couldn't find it. I try to look at it like this: the body of the lambda expression is parsed and all variables that aren't declared in the body are extracted. Then the current values of the variables is evaluated and bound to them. They do not change for the lifecycle of the resulting instance.
@SteffenT I don't understand. The variable will have stored some constant value. If that value is of a reference type, you can dereference it to invoke a method or access a field.
@SteffenT See my last code snippet. That is equivalent to what I think a compiler should do for your last snippet. The lambda will store the value of Foo.this in a private and final field. That is, it is immutable/constant. You can still access its field obj though. Or invoke any of its methods.
only this is captured, never instance variables of this. That's the behavior of Oracle javac too, as spec dictates. The issue originates from somewhere else. Unfortunately, eclipse is wrong here, per spec.
|
0

You can use a utility method to force capturing this only. This works with Java 9 too.

public static <T> T r(T object) { return object; } 

Now, you can rewrite your lambda like this:

Runnable run = () -> r(this).obj.toString(); 

Comments

0

I had a similar problem:

import java.util.function.Supplier; public class ObjectHolder { private final Object obj; public Supplier<Object> sup = () -> obj; // error public ObjectHolder(Object obj) { this.obj = obj; } } 

And resolved it this way:

public Supplier<Object> sup = () -> ((ObjectHolder)this).obj; 

Neither this.obj nor ObjectHolder.this.obj worked in Eclipse (though the latter worked for the standard JDK compiler).

In your case, use this workaround, it is safe for all compilers:

((Foo)this).obj.toString(); 

Another solution is to use a getter. In my example, it looks like this:

public Supplier<Object> sup = () -> getObj(); private Object getObj() { return obj; } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.