A private lambda function is accessing a private final
class member that is initialised in the class constructor. However, this code pattern compiles with error: variable num might not have been initialized
public class Main {
private final int num;
public Main() {num = 7;}
private java.util.function.Supplier getNum = () -> num;
public void printNum() {
System.out.println(getNum.get());
}
public static void main(String[] args) {
Main main = new Main();
main.printNum();
}
}
However, the following patterns are fine:
i. Move lambda definition inside a method
public class Main {
private final int num;
public Main() {num = 7;}
private java.util.function.Supplier getNum;
public void printNum() {
getNum = () -> num;
System.out.println(getNum.get());
}
public static void main(String[] args) {
Main main = new Main();
main.printNum();
}
}
ii. Local lambda inside a method
public class Main {
private final int num;
public Main() {num = 7;}
public void printNum() {
java.util.function.Supplier getNum = () -> num;
System.out.println(getNum.get());
}
public static void main(String[] args) {
Main main = new Main();
main.printNum();
}
}
iii. Add this
before the class member and cast this
to the class name
public class Main {
private final int num;
public Main() {num = 7;}
private java.util.function.Supplier getNum = () -> ((Main) this).num;
public void printNum() {
System.out.println(getNum.get());
}
public static void main(String[] args) {
Main main = new Main();
main.printNum();
}
}
Could someone please explain what is going on above for each case? If it is compiler specific, I was testing on OpenJDK 11. Thank you.
P.S. a solution so that no this
casting while the lambda can be shared among class methods is to also initialise it inside the class constructor
public class Main {
private final int num;
private final java.util.function.Supplier getNum;
public Main() {num = 7; getNum = () -> num;}
public void printNum() {
System.out.println(getNum.get());
}
public static void main(String[] args) {
Main main = new Main();
main.printNum();
}
}
The rules specified in Chapter 16 - Definite Assignment of the language specification are relevant here.
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=
.For every access of a local variable declared by a statement
x
, or blank final fieldx
,x
must be definitely assigned before the access, or a compile-time error occurs.
Just from the first paragraph, we can see that the expression ((Main) this).num
is not considered an "access" of a blank final field, as far as definite assignment is concerned, and therefore definite assignment analysis does not apply to it. Just writing num
, or this.num
are "accesses".
We will show that in the first code snippet, num
is not definitely assigned before the access in () -> num
.
16.9:
Let
C
be a class, and letV
be a blank final non-static member field ofC
, declared inC
. Then:
V
is definitely unassigned (and moreover is not definitely assigned) before the leftmost instance initializer or instance variable initializer ofC
.
() -> num
is the variable initialiser for the field getNum
. This is also the first (leftmost) variable initialiser of Main
. Therefore, num
is not definitely assigned before () -> num
.
Then 16.1.10 says:
If an expression is a lambda expression, then the following rules apply:
V
is definitely assigned before the expression or block that is the lambda body iffV
is definitely assigned before the lambda expression.
Notice that this is "iff", not just "if". As we have established, num
is not definitely assigned before the lambda expression, so num
is not definitely assigned before the lambda body expression, where the access occurs.
In cases i and ii, the access occurs in a method. 16.2.2 says:
A blank final member field
V
is definitely assigned (and moreover is not definitely unassigned) before the block that is the body of any method in the scope ofV
and before the declaration of any class declared within the scope ofV
.
Therefore, num
is already definitely assigned even before the method body. From here you can easily derive that it is definitely assigned before () -> num
, and therefore definitely assigned before the access.