This is a walk-through example I played to understand the lambda expression at Java bytecode level better and how to desugar it.
Consider the following source code example with the lamda-expressions in class Example
:
public interface Operator{
public int operation(int a, int b);
}
public class Addition{
public int operate(int a, int b, Operator op)
{
a=2*a;
b=2*b;
return op.operation(a,b);
}
}
public class Example{
public static void main(String... args){
Addition addition = new Addition();
addition.operate(1,2, (a,b)->a+b);
addition.operate(3,4, (a,b)->a+b+1);
}
}
Apply javap -verbose -p Example.class
after compilation, the bytecode of Èxample.class
looks like as follows (Full Version):
public class Example
...
{
...
public static void main(java.lang.String...);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
Code:
stack=4, locals=2, args_size=1
0: new #2 // class Addition
3: dup
4: invokespecial #3 // Method Addition."<init>":()V
7: astore_1
8: aload_1
9: iconst_1
10: iconst_2
11: invokedynamic #4, 0 // InvokeDynamic #0:operation:()LOperator;
16: invokevirtual #5 // Method Addition.operate:(IILOperator;)I
19: pop
20: aload_1
21: iconst_3
22: iconst_4
23: invokedynamic #6, 0 // InvokeDynamic #1:operation:()LOperator;
28: invokevirtual #5 // Method Addition.operate:(IILOperator;)I
31: pop
32: return
...
private static int lambda$main$1(int, int);
descriptor: (II)I
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: iconst_1
4: iadd
5: ireturn
...
private static int lambda$main$0(int, int);
descriptor: (II)I
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: ireturn
...
}
SourceFile: "Example.java"
InnerClasses:
public static final #55= #54 of #58; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #33 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:
#34 (II)I
#35 invokestatic Example.lambda$main$0:(II)I
#34 (II)I
1: #33 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:
#34 (II)I
#38 invokestatic Example.lambda$main$1:(II)I
#34 (II)I
The following bytecode are related to the lambda expressions (a+b)->a+b
and (a,b)->a+b+1
11: invokedynamic #4, 0 // InvokeDynamic #0:operation:()LOperator;
23: invokedynamic #6, 0 // InvokeDynamic #1:operation:()LOperator;
This first invokedynamic instruction will call:
0: #31 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:
#32 (II)I
#33 invokestatic Example.lambda$main$0:(II)I
#32 (II)I
The second will call:
1: #33 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:
#34 (II)I
#38 invokestatic Example.lambda$main$1:(II)I
#34 (II)I
The private static methods lambda$main$0(int, int)
and lambda$main$1(int, int)
are generated by the compliler and look like as follows:
private static int lambda$main$0(int, int);
descriptor: (II)I
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: ireturn
...
private static int lambda$main$1(int, int);
descriptor: (II)I
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: iconst_1
4: iadd
5: ireturn
...
Desugar Example.java
without using lambda expressions, but the above two methods generated by the compiler:
public class Example{
public static void main(String... args){
Addition addition = new Addition();
addition.operate(1,2, new Operator(){
public int operation(int a, int b)
{
return lambda$main$0(a, b);
}
});
addition.operate(3,4, new Operator(){
public int operation(int a, int b)
{
return lambda$main$1(a, b);
}
});
}
private static int lambda$main$0(int a, int b)
{
return a+b;
}
private static int lambda$main$1(int a, int b)
{
return a+b+1;
}
}
The corresponding bytecode looks like as follows (Full Version):
public class Example
...
public static void main(java.lang.String...);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
Code:
stack=5, locals=2, args_size=1
0: new #4 // class Addition
3: dup
4: invokespecial #5 // Method Addition."<init>":()V
7: astore_1
8: aload_1
9: iconst_1
10: iconst_2
11: new #6 // class Example$1
14: dup
15: invokespecial #7 // Method Example$1."<init>":()V
18: invokevirtual #8 // Method Addition.operate:(IILOperator;)I
21: pop
22: aload_1
23: iconst_3
24: iconst_4
25: new #9 // class Example$2
28: dup
29: invokespecial #10 // Method Example$2."<init>":()V
32: invokevirtual #8 // Method Addition.operate:(IILOperator;)I
35: pop
36: return
...
private static int lambda$main$0(int, int);
descriptor: (II)I
flags: ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: ireturn
...
private static int lambda$main$1(int, int);
descriptor: (II)I
flags: ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: iadd
3: iconst_1
4: iadd
5: ireturn
...
static int access$000(int, int);
descriptor: (II)I
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: invokestatic #2 // Method lambda$main$0:(II)I
5: ireturn
...
static int access$100(int, int);
descriptor: (II)I
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: invokestatic #1 // Method lambda$main$1:(II)I
5: ireturn
...
}
SourceFile: "Example.java"
InnerClasses:
static #9; //class Example$2
static #6; //class Example$1
The invokedynamic instruction:
11: invokedynamic #4, 0 // InvokeDynamic #0:operation:()LOperator;
is equivalent to the following instructions:
11: new #6 // class Example$1
14: dup
15: invokespecial #7 // Method Example$1."<init>":()V
To desugar lambda expression feature in the bytecode, we can rewrite the all the invokedynamic instructions with the equivalent instructions and inner classes as shown above.