The compiled bytecode of Java 8 lambda expressions
The compiled bytecode of Java 8 lambda expressions
In this article, I’d like to check the compiled code of the Java 8 lambda expressions. Here are the sample classes(See Lambda Expressions):
public class Calculator {
interface IntegerMath {
int operation(int a, int b);
}
public int operateBinary(int a, int b, IntegerMath op) {
return op.operation(a, b);
}
}
The above class defines a Calculator
class and an inner IntegerMath
interface. Now let’s use it in lambda expression. Here is the sample code:
public class PlayWithLambda {
public static void main(String[] args) throws Exception {
Calculator myApp = new Calculator();
Calculator.IntegerMath addition = (a, b) -> a + b;
Calculator.IntegerMath subtraction = (a, b) -> a - b;
myApp.operateBinary(40, 2, addition);
myApp.operateBinary(20, 10, subtraction);
}
}
The above class has two lines of lambda expressions that use the IntegerMath
interface. Now let compile the above classes to see how does the lambda expressions compiled to bytecode. Here is the command and its output:
$ ls
Calculator$IntegerMath.class Calculator.class Calculator.java PlayWithLambda.class PlayWithLambda.java
From the above command output, we can see the inner IntegerMath
interface is compiled to Calculator$IntegerMath.class
interface. In addition, the Calculator
and PlayWithLambda
are also compiled to its classes files.
Now let’s check the compiled code of Calculator.class
and the Calculator$IntegerMath.class
. Here is the bytecode of the Calculator.class
:
$ javap -c Calculator.class
Compiled from "Calculator.java"
public class Calculator {
public Calculator();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int operateBinary(int, int, Calculator$IntegerMath);
Code:
0: aload_3
1: iload_1
2: iload_2
3: invokeinterface #2, 3 // InterfaceMethod Calculator$IntegerMath.operation:(II)I
8: ireturn
}
The above bytecode does not contain anything special. We can see the invokespecial
instruction for the default constructor of the Calculator
class, and we can see the invokeinterface
instruction in operateBinary(...)
method to call the Calculator$IntegerMath.operation(...)
method. Here is the bytecode of the Calculator$IntegerMath.class
:
$ javap -c Calculator\$IntegerMath.class
Compiled from "Calculator.java"
interface Calculator$IntegerMath {
public abstract int operation(int, int);
}
The above class file just contain the signature of the interface. Now let’s check the bytecode of the PlayWithLambda.class
.
$ javap -c PlayWithLambda.class
Compiled from "PlayWithLambda.java"
public class PlayWithLambda {
public PlayWithLambda();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class Calculator
3: dup
4: invokespecial #3 // Method Calculator."<init>":()V
7: astore_1
8: invokedynamic #4, 0 // InvokeDynamic #0:operation:()LCalculator$IntegerMath;
13: astore_2
14: invokedynamic #5, 0 // InvokeDynamic #1:operation:()LCalculator$IntegerMath;
19: astore_3
20: aload_1
21: bipush 40
23: iconst_2
24: aload_2
25: invokevirtual #6 // Method Calculator.operateBinary:(IILCalculator$IntegerMath;)I
28: pop
29: aload_1
30: bipush 20
32: bipush 10
34: aload_3
35: invokevirtual #6 // Method Calculator.operateBinary:(IILCalculator$IntegerMath;)I
38: pop
39: return
}
From the above bytecode, we can see the two lines related with the lambda expression:
8: invokedynamic #4, 0 // InvokeDynamic #0:operation:()LCalculator$IntegerMath;
14: invokedynamic #5, 0 // InvokeDynamic #1:operation:()LCalculator$IntegerMath;
The above lines are related with the following Java code:
Calculator.IntegerMath addition = (a, b) -> a + b;
Calculator.IntegerMath subtraction = (a, b) -> a - b;
We can see the Java compiler will compile the initialization code of the lambda expression code into invokedyanmic
instructions. If you don’t know invokedynamic
instruction yet, you can check this article: Invokedynamic - Java’s Secret Weapon. I will discuss the detail of invokedyanmic
instruction in another article.
Next there are two lines of bytecode related with the usage of the lambda expression:
25: invokevirtual #6 // Method Calculator.operateBinary:(IILCalculator$IntegerMath;)I
35: invokevirtual #6 // Method Calculator.operateBinary:(IILCalculator$IntegerMath;)I
The above two lines of code are related with the following Java code:
myApp.operateBinary(40, 2, addition);
myApp.operateBinary(20, 10, subtraction);
From the above code, we can see using the classes created from lambda expressions are just plain invokevirtual
instructions.
We have checked the bytecode and its relationship with the Java code. This time I will use the -verbose
option to disassemble the code to get all the symbols from the class file:
Classfile /Users/weli/Desktop/lambda/PlayWithLambda.class
Last modified May 22, 2017; size 1161 bytes
MD5 checksum 54fe2e615e51b6eea59f94252110b2c9
Compiled from "PlayWithLambda.java"
public class PlayWithLambda
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // Calculator
#3 = Methodref #2.#22 // Calculator."<init>":()V
#4 = InvokeDynamic #0:#28 // #0:operation:()LCalculator$IntegerMath;
#5 = InvokeDynamic #1:#28 // #1:operation:()LCalculator$IntegerMath;
#6 = Methodref #2.#30 // Calculator.operateBinary:(IILCalculator$IntegerMath;)I
#7 = Class #31 // PlayWithLambda
#8 = Class #32 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 Exceptions
#16 = Class #33 // java/lang/Exception
#17 = Utf8 lambda$main$1
#18 = Utf8 (II)I
#19 = Utf8 lambda$main$0
#20 = Utf8 SourceFile
#21 = Utf8 PlayWithLambda.java
#22 = NameAndType #9:#10 // "<init>":()V
#23 = Utf8 Calculator
#24 = Utf8 BootstrapMethods
#25 = MethodHandle #6:#34 // 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;
#26 = MethodType #18 // (II)I
#27 = MethodHandle #6:#35 // invokestatic PlayWithLambda.lambda$main$0:(II)I
#28 = NameAndType #36:#40 // operation:()LCalculator$IntegerMath;
#29 = MethodHandle #6:#41 // invokestatic PlayWithLambda.lambda$main$1:(II)I
#30 = NameAndType #42:#43 // operateBinary:(IILCalculator$IntegerMath;)I
#31 = Utf8 PlayWithLambda
#32 = Utf8 java/lang/Object
#33 = Utf8 java/lang/Exception
#34 = Methodref #44.#45 // 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;
#35 = Methodref #7.#46 // PlayWithLambda.lambda$main$0:(II)I
#36 = Utf8 operation
#37 = Class #47 // Calculator$IntegerMath
#38 = Utf8 IntegerMath
#39 = Utf8 InnerClasses
#40 = Utf8 ()LCalculator$IntegerMath;
#41 = Methodref #7.#48 // PlayWithLambda.lambda$main$1:(II)I
#42 = Utf8 operateBinary
#43 = Utf8 (IILCalculator$IntegerMath;)I
#44 = Class #49 // java/lang/invoke/LambdaMetafactory
#45 = NameAndType #50:#53 // 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;
#46 = NameAndType #19:#18 // lambda$main$0:(II)I
#47 = Utf8 Calculator$IntegerMath
#48 = NameAndType #17:#18 // lambda$main$1:(II)I
#49 = Utf8 java/lang/invoke/LambdaMetafactory
#50 = Utf8 metafactory
#51 = Class #55 // java/lang/invoke/MethodHandles$Lookup
#52 = Utf8 Lookup
#53 = Utf8 (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;
#54 = Class #56 // java/lang/invoke/MethodHandles
#55 = Utf8 java/lang/invoke/MethodHandles$Lookup
#56 = Utf8 java/lang/invoke/MethodHandles
{
public PlayWithLambda();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=4, args_size=1
0: new #2 // class Calculator
3: dup
4: invokespecial #3 // Method Calculator."<init>":()V
7: astore_1
8: invokedynamic #4, 0 // InvokeDynamic #0:operation:()LCalculator$IntegerMath;
13: astore_2
14: invokedynamic #5, 0 // InvokeDynamic #1:operation:()LCalculator$IntegerMath;
19: astore_3
20: aload_1
21: bipush 40
23: iconst_2
24: aload_2
25: invokevirtual #6 // Method Calculator.operateBinary:(IILCalculator$IntegerMath;)I
28: pop
29: aload_1
30: bipush 20
32: bipush 10
34: aload_3
35: invokevirtual #6 // Method Calculator.operateBinary:(IILCalculator$IntegerMath;)I
38: pop
39: return
LineNumberTable:
line 3: 0
line 4: 8
line 5: 14
line 7: 20
line 8: 29
line 9: 39
Exceptions:
throws java.lang.Exception
}
SourceFile: "PlayWithLambda.java"
InnerClasses:
static #38= #37 of #2; //IntegerMath=class Calculator$IntegerMath of class Calculator
public static final #52= #51 of #54; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #25 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:
#26 (II)I
#27 invokestatic PlayWithLambda.lambda$main$0:(II)I
#26 (II)I
1: #25 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:
#26 (II)I
#29 invokestatic PlayWithLambda.lambda$main$1:(II)I
#26 (II)I
The above output is very long, and I will pick out the lines related with lambda expressions. Here is the relative code:
#4 = InvokeDynamic #0:#28 // #0:operation:()LCalculator$IntegerMath;
#5 = InvokeDynamic #1:#28 // #1:operation:()LCalculator$IntegerMath;
The above two symbols are the invokedynamic
instructions related with the creation of the two lambda expressions.
#17 = Utf8 lambda$main$1
#19 = Utf8 lambda$main$0
The above two lines are the anonymous classes created by the lambda classes. So we can see the lambda expression will just create anonymous classes in bytecode level.
#48 = NameAndType #17:#18 // lambda$main$1:(II)I
#49 = Utf8 java/lang/invoke/LambdaMetafactory
#50 = Utf8 metafactory
#51 = Class #55 // java/lang/invoke/MethodHandles$Lookup
#52 = Utf8 Lookup
#53 = Utf8 (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;
#54 = Class #56 // java/lang/invoke/MethodHandles
#55 = Utf8 java/lang/invoke/MethodHandles$Lookup
#56 = Utf8 java/lang/invoke/MethodHandles
The above code are related with invokedynamic
feature and we can see classes like java/lang/invoke/LambdaMetafactory
and java/lang/invoke/MethodHandles
. I won’t explain the detail of the invokedynamic
instruction and its supporting classes in this article.
In conclusion, the lambda expression is supported by invokedynamic
instruction and the lambda expression itself will be compiled to anonymous class.