I have an anonymous inner class and an equivalent lambda. Why are the variable initialization rules stricter for the lambda, and is there a solution cleaner than an anonymous inner class or initializing it in the constructor?
import java.util.concurrent.Callable;
public class Immutable {
private final int val;
public Immutable(int val) { this.val = val; }
// Works fine
private final Callable<String> anonInnerGetValString = new Callable<String>() {
@Override
public String call() throws Exception {
return String.valueOf(val);
}
};
// Doesn't compile; "Variable 'val' might not have been initialized"
private final Callable<String> lambdaGetValString = () -> String.valueOf(val);
}
Edit: I did run across one workaround: using a getter for val
.
Anonymous class is an inner class without a name, which means that we can declare and instantiate class at the same time. A lambda expression is a short form for writing an anonymous class. By using a lambda expression, we can declare methods without any name.
Lambda expression can be used where a class implements a functional interface to reduce the complexity of the code. An inner anonymous class is more powerful as we can use many methods as we want, whereas lambda expression can only be used where an interface has only a single abstract method.
A local inner class consists of a class declared within a method, whereas an anonymous class is declared when an instance is created. So the anonymous class is created on the fly or during program execution.
The chapter on lambda expression bodies states
Unlike code appearing in anonymous class declarations, the meaning of names and the
this
andsuper
keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).The transparency of
this
(both explicit and implicit) in the body of a lambda expression - that is, treating it the same as in the surrounding context - allows more flexibility for implementations, and prevents the meaning of unqualified names in the body from being dependent on overload resolution.
They're more strict because of that.
The surrounding context, in this case, is an assignment to a field and the issue at hand is an access of a field, val
, a blank final
field, in the right hand side of the expression.
The Java Language Specification states
Each local variable (§14.4) and every blank
final
field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.An access to its value consists of the simple name of the variable (or, for a field, the simple name of the field qualified by
this
) occurring anywhere in an expression except as the left-hand operand of the simple assignment operator=
(§15.26.1).For every access of a local variable or blank
final
fieldx
,x
must be definitely assigned before the access, or a compile-time error occurs.
It then goes on to say
Let
C
be a class, and letV
be a blankfinal
non-static
member field ofC
, declared inC
. Then:
V
is definitely unassigned (and moreover is not definitely assigned) before the leftmost instance initializer (§8.6) or instance variable initializer ofC
.
V
is [un]assigned before an instance initializer or instance variable initializer ofC
other than the leftmost iffV
is [un]assigned after the preceding instance initializer or instance variable initializer ofC
.
Your code basically looks like this
private final int val;
// leftmost instance variable initializer, val still unassigned
private final Callable<String> anonInnerGetValString = ...
// still unassigned after preceding variable initializer
private final Callable<String> lambdaGetValString = ...
The compiler therefore determines that val
in unassigned when it's accessed within the initialization expression for lambdaGetValString
.
This won't compile:
public class Example
{
private final int x;
private final int y = 2 * x;
public Example() {
x = 10;
}
}
but this will:
public class Example
{
private final int x;
private final int y;
public Example() {
x = 10;
y = 2 * x;
}
}
and so will this:
public class Example
{
private final int x = 10;
private final int y = 2 * x;
}
So it's nothing to do with lambdas. A field that is initialized on the same line that it is declared on is evaluated before the constructor is executed. So at that point, the variable 'val' (or in this example 'x') has not been initialized.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With