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.