"Effectively final" is not technically required, it could've been done without. But the language designers put this restriction to avoid confusion, because if the variable kept changing, what value would the lambda see, the initial, or the latest? Other languages that have lambda don't have this restriction, and the spec sets the expectation for this use case.
Given the following code:
import java.util.function.Consumer;
class Main {
public static void main(String args[]) {
int i = 42;
final int j = 41;
for (int k = 0; k < 5; k++) {
Consumer<String> x = msg -> System.out.printf("x=%s, i=%d%n", msg, i);
Consumer<String> y = msg -> System.out.printf("y=%s, j=%d%n", msg, j);
Consumer<String> z = msg -> System.out.printf("z=%s%n", msg);
x.accept(x.toString());
y.accept(y.toString());
z.accept(z.toString());
}
}
}
When we inspect the generated bytecode with javap -c -v Main.class
, we see:
11: invokedynamic #7, 0 // InvokeDynamic #0:accept:(I)Ljava/util/function/Consumer;
16: astore_3
17: invokedynamic #11, 0 // InvokeDynamic #1:accept:()Ljava/util/function/Consumer;
22: astore 4
24: invokedynamic #14, 0 // InvokeDynamic #2:accept:()Ljava/util/function/Consumer;
We can see how the lambdas are translated. The corresponding static
methods show that the first lambda is a capturing lambda and has an integer 1st parameter (#71) after translation, while the other ones don't.
BootstrapMethods:
0: #63 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#70 (Ljava/lang/Object;)V
#71 REF_invokeStatic Main.lambda$main$0:(ILjava/lang/String;)V
#74 (Ljava/lang/String;)V
1: #63 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#70 (Ljava/lang/Object;)V
#75 REF_invokeStatic Main.lambda$main$1:(Ljava/lang/String;)V
#74 (Ljava/lang/String;)V
2: #63 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#70 (Ljava/lang/Object;)V
#78 REF_invokeStatic Main.lambda$main$2:(Ljava/lang/String;)V
#74 (Ljava/lang/String;)V
So, it's just how lambdas are translated. You can find more details in this article.