I am executing below in main method:
int secrete = 42;
for (int i = 0; i < 5; i ) {
Consumer<String> myprinter2 =
msg -> {
System.out.println("consuming " msg " ," secrete);
};
myprinter2.accept(myprinter2.toString());
}
The output for the above code is:
consuming Main$$Lambda$1/1324119927@6d311334 ,42
consuming Main$$Lambda$1/1324119927@682a0b20 ,42
consuming Main$$Lambda$1/1324119927@3d075dc0 ,42
consuming Main$$Lambda$1/1324119927@214c265e ,42
consuming Main$$Lambda$1/1324119927@448139f0 ,42
If I change secrete
to be final, then the output is:
consuming Main$$Lambda$1/2003749087@41629346 ,42
consuming Main$$Lambda$1/2003749087@41629346 ,42
consuming Main$$Lambda$1/2003749087@41629346 ,42
consuming Main$$Lambda$1/2003749087@41629346 ,42
consuming Main$$Lambda$1/2003749087@41629346 ,42
secrete
is effectively final even if I do not declare it final, so why is each lambda considered a new object when I do not declare it final?
CodePudding user response:
This prints false
:
int x = 42;
String a = "hi" x;
String b = "hi" x;
System.out.println(a == b);
This prints true
:
final int x = 42;
String a = "hi" x;
String b = "hi" x;
System.out.println(a == b);
So, final
influences if the concatenation " ," secrete
in System.out.println("consuming " msg " ," secrete);
is done at compile time. Without final
, " ," secrete
results in a different String
object in every iteration and therefore, JIT has to create a new Consumer
in each iteration. With final
, System.out.println("consuming " msg " ," secrete);
depends only on msg
and therefore, only one Consumer
is created.
CodePudding user response:
"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.