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 t
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();
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;
}
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.
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.