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.