Skip to content

Instantly share code, notes, and snippets.

@linghuiluo
Last active April 19, 2020 18:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save linghuiluo/5acb99a3f1253b556ef10d32d6077f0c to your computer and use it in GitHub Desktop.
Save linghuiluo/5acb99a3f1253b556ef10d32d6077f0c to your computer and use it in GitHub Desktop.
Lambda Expression From Java Source to Bytecode & Desugaring in Bytecode

Lambda Expression From Java Source to Bytecode & Desugaring

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment