由浅入深学习java8的Lambda原理

招聘信息:盒马-Java技术专家-淘鲜达
有兴趣的小伙伴可以直接把简历发到我邮箱: hongboyuan.yhb@alibaba-inc.com

java8lambda由浅入深,通过一个简单例子,逐步深入了解lambda实现原理。

一个简单的java8中lambda例子

先看一个简单的使用lambda的例子,我们从这个例子开始逐步探索java8中lambda是如何实现的。

/**
 * Created by qiyan on 2017/4/16.
 */
public class LambdaTest {

    public static void main(String[] args) {
        Func add = (x, y) -> x + y;
        System.out.println(add.exec(1, 2));
    }
}


@FunctionalInterface
interface Func {
    int exec(int x, int y);
}

上面源码编译完成后执行 javap -p -v -c LambdaTest 查看反编译结果:

Classfile /Users/qiyan/src/test/src/main/java/LambdaTest.class
  Last modified 2017-4-16; size 969 bytes
  MD5 checksum 0a1db458a90b20fbfae645b576725fd4
  Compiled from "LambdaTest.java"
public class LambdaTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#18         // java/lang/Object."<init>":()V
   #2 = InvokeDynamic      #0:#23         // #0:exec:()LFunc;
   #3 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = InterfaceMethodref #26.#27        // Func.exec:(II)I
   #5 = Methodref          #28.#29        // java/io/PrintStream.println:(I)V
   #6 = Class              #30            // LambdaTest
   #7 = Class              #31            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               lambda$main$0
  #15 = Utf8               (II)I
  #16 = Utf8               SourceFile
  #17 = Utf8               LambdaTest.java
  #18 = NameAndType        #8:#9          // "<init>":()V
  #19 = Utf8               BootstrapMethods
  #20 = MethodHandle       #6:#32         // 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;
  #21 = MethodType         #15            //  (II)I
  #22 = MethodHandle       #6:#33         // invokestatic LambdaTest.lambda$main$0:(II)I
  #23 = NameAndType        #34:#35        // exec:()LFunc;
  #24 = Class              #36            // java/lang/System
  #25 = NameAndType        #37:#38        // out:Ljava/io/PrintStream;
  #26 = Class              #39            // Func
  #27 = NameAndType        #34:#15        // exec:(II)I
  #28 = Class              #40            // java/io/PrintStream
  #29 = NameAndType        #41:#42        // println:(I)V
  #30 = Utf8               LambdaTest
  #31 = Utf8               java/lang/Object
  #32 = Methodref          #43.#44        // 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;
  #33 = Methodref          #6.#45         // LambdaTest.lambda$main$0:(II)I
  #34 = Utf8               exec
  #35 = Utf8               ()LFunc;
  #36 = Utf8               java/lang/System
  #37 = Utf8               out
  #38 = Utf8               Ljava/io/PrintStream;
  #39 = Utf8               Func
  #40 = Utf8               java/io/PrintStream
  #41 = Utf8               println
  #42 = Utf8               (I)V
  #43 = Class              #46            // java/lang/invoke/LambdaMetafactory
  #44 = NameAndType        #47:#51        // 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;
  #45 = NameAndType        #14:#15        // lambda$main$0:(II)I
  #46 = Utf8               java/lang/invoke/LambdaMetafactory
  #47 = Utf8               metafactory
  #48 = Class              #53            // java/lang/invoke/MethodHandles$Lookup
  #49 = Utf8               Lookup
  #50 = Utf8               InnerClasses
  #51 = 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;
  #52 = Class              #54            // java/lang/invoke/MethodHandles
  #53 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #54 = Utf8               java/lang/invoke/MethodHandles
{
  public LambdaTest();
    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 4: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:exec:()LFunc;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: iconst_1
        11: iconst_2
        12: invokeinterface #4,  3            // InterfaceMethod Func.exec:(II)I
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        20: return
      LineNumberTable:
        line 7: 0
        line 8: 6
        line 9: 20

  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
      LineNumberTable:
        line 7: 0
}
SourceFile: "LambdaTest.java"
InnerClasses:
     public static final #49= #48 of #52; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #20 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:
      #21 (II)I
      #22 invokestatic LambdaTest.lambda$main$0:(II)I
      #21 (II)I

根据反编译结果,不难看出lambda表达式:(x, y) -> x + y 被编译成了一个方法:lambdamain0

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
      LineNumberTable:
        line 7: 0

翻译成java代码:

private static int lambda$main$0(int x, int y){
    return x + y;
}

再看看main方法字节码:

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:exec:()LFunc;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: iconst_1
        11: iconst_2
        12: invokeinterface #4,  3            // InterfaceMethod Func.exec:(II)I
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        20: return
      LineNumberTable:
        line 7: 0
        line 8: 6
        line 9: 20

执行main步骤:

0:通过invokedynamic指令生成调用对象;
5:存入本地变量表;
6:加载java.lang.System.out静态方法;
9:将lambda表达式生成的对象加载入执行栈;
10:将int类型1加载入执行栈;
11:将int类型2加载入执行栈;
12:执行lambda表达式生成的对象的exec方法;
17:输出执行结果;

lambda的要点就在invokedynamic这个指令了,通过invokedynamic指令生成目标对象,接下来我们了解一下invokedynamic指令。

invokedynamic指令

下面重点看看invokedynamic指令,首先我们来看看常量池中出现的的InvokeDynamic类型:

官方文档:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.10

CONSTANT_InvokeDynamic_info {
    u1 tag;//InvokeDynamic类型标记18
    u2 bootstrap_method_attr_index; //BootstrapMethods_attribute中的坐标
    u2 name_and_type_index; //名字&类型常量池坐标
}

接下来看看BootstrapMethods_attribute,InvokeDynamic中的bootstrap_method_attr_index就是指向其中bootstrap_methods的下标:
官方文档:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.21

BootstrapMethods_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 num_bootstrap_methods;
    {   u2 bootstrap_method_ref;//方法引用
        u2 num_bootstrap_arguments;//参数数量
        u2 bootstrap_arguments[num_bootstrap_arguments];//参数
    } bootstrap_methods[num_bootstrap_methods];
}

继续bootstrap_methods中的bootstrap_method_ref:

官方文档:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.8

CONSTANT_MethodHandle_info {
    u1 tag;//MethodHandle类型标记15
    u1 reference_kind;//方法引用类型getfield/getstatic/putfield/putstatic/invokevirtual/invokestatic/invokespecial/new/invokeinterface,此例中使用的invokestatic。
    u2 reference_index;//引用类型,根据reference_kind确认,例如本例中kind为方法调用,所以index为Methodref,细节可以查看官方文档。
}

回归lambda例子

对invokedynamic学习总结一下:
invokedynamic指令通过找到BootstrapMethods中的方法,生成动态调用点,对于本例,我们对照BootstrapMethods中的bootstrap_methods分析实现:

BootstrapMethods:
  0: #20 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:
      #21 (II)I
      #22 invokestatic LambdaTest.lambda$main$0:(II)I
      #21 (II)I

按照上面指令,可以看出,invokedynamic指令通过java.lang.invoke.LambdaMetafactory#metafactory方法生成目标对象。

public static CallSite metafactory(MethodHandles.Lookup caller,String invokedName,MethodType invokedType,MethodType samMethodType,MethodHandle implMethod,MethodType instantiatedMethodType) throws LambdaConversionException {

    AbstractValidatingLambdaMetafactory mf;

    mf = new InnerClassLambdaMetafactory(caller,invokedType,invokedName,samMethodType,implMethod,instantiatedMethodType,false,EMPTY_CLASS_ARRAY,EMPTY_MT_ARRAY);

    mf.validateMetafactoryArgs();

    return mf.buildCallSite();
}

查看相关代码可以看出,metafactory就是核心方法,该方法通过InnerClassLambdaMetafactory类生成对象,供后续调用,在InnerClassLambdaMetafactory源码中可以看到,有提供开关是否dump生成的class文件。

private static final ProxyClassesDumper dumper;

static {
    final String key = "jdk.internal.lambda.dumpProxyClasses";
    String path = AccessController.doPrivileged(new GetPropertyAction(key), null,new PropertyPermission(key , "read"));
    dumper = (null == path) ? null : ProxyClassesDumper.getInstance(path);
}

//dump逻辑
if (dumper != null) {
  AccessController.doPrivileged(new PrivilegedAction<Void>() {
    @Override
    public Void run() {
    dumper.dumpClass(lambdaClassName, classBytes);
    return null;
    }
  }, null,
    new FilePermission("<<ALL FILES>>", "read, write"),
    // createDirectories may need it
    new PropertyPermission("user.dir", "read"));
}

执行下面命令生成中间对象java -Djdk.internal.lambda.dumpProxyClasses LambdaTest

Classfile /Users/qiyan/src/test/src/main/java/LambdaTest$$Lambda$1.class
  Last modified 2017-4-17; size 236 bytes
  MD5 checksum 983fa2b5e7d29c46d6f885925909b83e
final class LambdaTest$$Lambda$1 implements Func
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
   #1 = Utf8               LambdaTest$$Lambda$1
   #2 = Class              #1             // LambdaTest$$Lambda$1
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Func
   #6 = Class              #5             // Func
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = NameAndType        #7:#8          // "<init>":()V
  #10 = Methodref          #4.#9          // java/lang/Object."<init>":()V
  #11 = Utf8               exec
  #12 = Utf8               (II)I
  #13 = Utf8               LambdaTest
  #14 = Class              #13            // LambdaTest
  #15 = Utf8               lambda$main$0
  #16 = NameAndType        #15:#12        // lambda$main$0:(II)I
  #17 = Methodref          #14.#16        // LambdaTest.lambda$main$0:(II)I
  #18 = Utf8               Code
{
  private LambdaTest$$Lambda$1();
    descriptor: ()V
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return

  public int exec(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: invokestatic  #17                 // Method LambdaTest.lambda$main$0:(II)I
         5: ireturn
}

第一个例子学习分析到此结束,下面是根据原理,翻译的等价的最终执行代码。

public class LambdaTest {

    public static void main(String[] args) {
        Func add = new LambdaTest$$Lambda$1();
        System.out.println(add.exec(1, 2));
    }

    private static int lambda$main$0(int x, int y) {
        return x + y;
    }

    static final class LambdaTest$$Lambda$1 implements Func {
        private LambdaTest$$Lambda$1() {
        }

        public int exec(int x, int y) {
            return LambdaTest.lambda$main$0(x, y);
        }
    }
}


@FunctionalInterface
interface Func {
    int exec(int x, int y);
}

后续有时间继续补充变量作用域相关实现原理...

推荐阅读更多精彩内容

  • java里的Lambda Expression想必大家都已经很清楚是个什么东西了(什么?不清楚?不清楚我也不多BB...
    89342aed9b18阅读 2,109评论 0 5
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 75,848评论 12 117
  • 百战程序员_ Java1573题 QQ群:561832648489034603 掌握80%年薪20万掌握50%年薪...
    Albert陈凯阅读 10,531评论 2 30
  • 今日体验,再一次感受到语言的强大,但是反思忏悔,真正强大的是需要吗?错了,需要的背后一定是需要系统的支撑,体验二,...
    王海博阅读 50评论 0 0
  • 随着年龄的增长,很多东西不再奢望谁买给自己。因为我也可以。 很小的时候,羡慕别的孩子有可爱的洋娃娃,缠着老爸要了很...
    梅园遗珠阅读 176评论 0 0