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 anonymou
The chapter on lambda expression bodies states
Unlike code appearing in anonymous class declarations, the meaning of names and the
thisandsuperkeywords 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
finalfield (§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
finalfieldx,xmust be definitely assigned before the access, or a compile-time error occurs.
It then goes on to say
Let
Cbe a class, and letVbe a blankfinalnon-staticmember field ofC, declared inC. Then:
Vis definitely unassigned (and moreover is not definitely assigned) before the leftmost instance initializer (§8.6) or instance variable initializer ofC.
Vis [un]assigned before an instance initializer or instance variable initializer ofCother than the leftmost iffVis [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 anonInnerGetValString = ...
// still unassigned after preceding variable initializer
private final Callable lambdaGetValString = ...
The compiler therefore determines that val in unassigned when it's accessed within the initialization expression for lambdaGetValString.
The rules above apply to the use of a simple name, val, not to a qualified expression, this.val. You can use
final Callable lambdaGetValString = () -> String.valueOf(this.val);