美文网首页JavaSEjavaTool
深入理解Lambda表达式

深入理解Lambda表达式

作者: tracy_668 | 来源:发表于2019-07-06 15:33 被阅读378次

    什么是Lambda表达式?

    对于一个Java变量,我们可以赋给其一个“值”。


    image.png

    如果想把“一块代码”赋给一个Java变量,应该怎么做呢?比如把右边那块代码,赋给一个叫做aBlockOfCode的Java变量:

    image.png

    在Java 8之前,这个是做不到的。但是Java 8之后,利用Lambda特性就可以做到,如图:

    image.png

    当然,这个并不是很简洁的写法。所以,为了使这个赋值操作更加简洁,我们可以移除一些没用的声明,如下:

    image.png

    在 lambda 表达式的一个参数中省略类型,Java 需要通过上下文细节来推断该类型。我们就成功的非常优雅的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个Lambda表达式。变量aBlockOfCode的类型应该是什么?

    在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是“那段代码”,需要是这个接口的实现。简而言之,Lambda表达式本身就是一个接口的实现


    image.png

    这种只有一个接口函数需要被实现的接口类型,我们叫它“函数式接口”。为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface,这样别人就无法在里面添加新的接口函数了:

    image.png

    这样,就得到了一个完整的Lambda表达式声明:

    image.png

    Lambda表达式作用

    最直观的作用就是使得代码变得异常简洁。

    image.png image.png

    Lambda结合FunctionalInterface Lib, forEach, stream(),method reference等新特性可以使代码变的更加简洁!
    假设Person的定义和List<Person>的值都给定:

    image.png

    现在需要打印出guiltyPersons List里面所有LastName以"Z"开头的人的FirstName。
    原生态Lambda写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值Lambda表达式。


    image.png

    在Java 8中有一个函数式接口的包,里面定义了大量可能用到的函数式接口(java.util.function (Java Platform SE 8 ))。所以,我们在这里压根都不需要定义NameChecker和Executor这两个函数式接口,直接用Java 8函数式接口包里的Predicate<T>和Consumer<T>就可以了——因为它们这一对的接口定义和NameChecker/Executor其实是一样的。

    image.png

    第一步简化 - 利用函数式接口包:


    image.png

    静态函数里面的for each循环其实是非常碍眼的。这里可以利用Iterable自带的forEach()来替代。forEach()本身可以接受一个Consumer<T> 参数。

    第二步简化 - 用Iterable.forEach()取代foreach loop:


    image.png

    由于静态函数其实只是对List进行了一通操作,这里我们可以甩掉静态函数,直接使用stream()特性来完成。stream()的几个方法都是接受Predicate<T>,Consumer<T>等参数的(java.util.stream (Java Platform SE 8 ))

    第三步简化 - 利用stream()替代静态函数:

    image.png

    对比最开始的Lambda写法,这里已经非常非常简洁了。但是如果,我们要求变一下,变成print这个人的全部信息,及p -> System.out.println(p); 那么还可以利用Method reference来继续简化。所谓Method reference, 就是用已经写好的别的Object/Class的method来代替Lambda expression。

    image.png

    第四步简化 - 如果是println(p),则可以利用Method reference代替forEach中的Lambda表达式:

    image.png

    Lambda配合Optional<T>可以使Java对于null的处理变的异常优雅

    这里假设我们有一个person object,以及一个person object的Optional wrapper:

    image.png

    Optional<T>如果不结合Lambda使用的话,并不能使原来繁琐的null check变的简单。


    image.png

    只有当Optional<T>结合Lambda一起使用的时候,才能发挥出其真正的威力!


    image.png image.png image.png image.png

    深入源码解读

    一个简单的java8中lambda例子

    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 被编译成了一个方法: private static int lambdamain0(int, int);

    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指令

    它的目的在于由用户代码通过方法句柄API(method handles API)在运行时确定如何分派.
    方法句柄(method handle)。它代表了一个可以从invokedynamic调用点进行调用的方法。这里的基本理念就是每个invokedynamic指令都会与一个特定的方法关联(也就是引导方法或BSM)。当解释器(interpreter)遇到invokedynamic指令的时候,BSM会被调用。它会返回一个对象(包含了一个方法句柄),这个对象表明了调用点要实际执行哪个方法。

    invokedynamic指令通过引导方法(bootstrap method,BSM)机制来使用方法句柄。与invokevirtual指令不同,invokedynamic指令没有接收者对象。相反,它们的行为类似于invokestatic,会使用BSM来返回一个CallSite类型的对象。这个对象包含一个方法句柄(称之为“target”),它代表了当前invokedynamic指令要执行的方法。

    我们来看一下基于ASM的类,它会使用invokedynamic指令来生成“Hello World”。

    public class InvokeDynamicCreator {
     
        public static void main(final String[] args) throws Exception {
            final String outputClassName = "kathik/Dynamic";
            try (FileOutputStream fos
                    = new FileOutputStream(new File("target/classes/" + outputClassName + ".class"))) {
                fos.write(dump(outputClassName, "bootstrap", "()V"));
            }
        }
     
        public static byte[] dump(String outputClassName, String bsmName, String targetMethodDescriptor)
                throws Exception {
            final ClassWriter cw = new ClassWriter(0);
            MethodVisitor mv;
     
            // 为引导类搭建基本的元数据
            cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, outputClassName, null, "java/lang/Object", null);
     
            // 创建标准的void构造器
            mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
     
            // 创建标准的main方法
            mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
            mv.visitCode();
            MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
                    MethodType.class);
            Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, "kathik/InvokeDynamicCreator", bsmName,
                    mt.toMethodDescriptorString());
            mv.visitInvokeDynamicInsn("runDynamic", targetMethodDescriptor, bootstrap);
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 1);
            mv.visitEnd();
     
            cw.visitEnd();
     
            return cw.toByteArray();
        }
     
        private static void targetMethod() {
            System.out.println("Hello World!");
        }
     
        public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
            final MethodHandles.Lookup lookup = MethodHandles.lookup();
            // 需要使用lookupClass(),因为这个方法是静态的
            final Class currentClass = lookup.lookupClass();
            final MethodType targetSignature = MethodType.methodType(void.class);
            final MethodHandle targetMH = lookup.findStatic(currentClass, "targetMethod", targetSignature);
            return new ConstantCallSite(targetMH.asType(type));
        }
    }
    

    这个代码分为两部分,第一部分使用ASM Visitor API来创建名为kathik.Dynamic的类文件。注意,核心的调用是visitInvokeDynamicInsn()。第二部分包含了要捆绑到调用点中的目标方法,并且还包括invokedynamic指令所需的BSM。
    当InvokeDynamicCreator运行时,它会创建一个新的类文件Dynamic.class,这个文件中包含了invokedynamic指令,通过在这个类上执行javap,我们可以看到这一点:

    public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=0, locals=1, args_size=1
             0: invokedynamic #20,  0             // InvokeDynamic #0:runDynamic:()V
             5: return
    

    具体到本文例子
    invokedynamic的操作数(operand)指向常量池一个动态调用点描述符(dynamic call site specifier)。 动态调用点描述符是一个CONSTANT_InvokeDynamic_info结构体:

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

    tag 表示这个结构体的常量,不用管
    bootstrap_method_attr_index 启动方法数组
    name_and_type_index 一个名字+类型的描述字段,就像这样Object p放到虚拟机里面表示是Ljava/lang/Object; p

    接下来看看BootstrapMethods_attribute,InvokeDynamic中的bootstrap_method_attr_index就是指向其中bootstrap_methods的下标:

    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];
    }
    

    就是一个数组,每个元素是{指向MethodHandle的索引,启动方法参数个数,启动方法参数}
    MethodlHandle是个非常重要的结构,指导了虚拟机对于这个启动方法的解析,先关注一下这个结构:

    继续bootstrap_methods中的bootstrap_method_ref:

    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,细节可以查看官方文档。
    }
    

    继续bootstrap_methods中的bootstrap_method_ref:

    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,细节可以查看官方文档。
    }
    

    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,我们可以得

    名字+描述符的表示(由name_and_type_index给出)
    一个启动方法数组(由bootstrap_method_attr_index给出)

    按照上面指令,可以看出,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);
    }
    
    

    再来看另外一个例子:

    interface Encode {
        void encode(Derive person);
    }
    class Base {
        public void encrypt() {
            System.out.println("Base::speak");
        }
    }
    class Derive extends Base {
        @Override
        public void encrypt() {
            System.out.println("Derive::speak");
        }
    }
    public class MethodReference {
        public static void main(String[] args) {
            Encode encode = Base::encrypt;
            System.out.println(encode);
        }
    }
    

    使用javap -verbose MethodReference.class查看对应字节码:

    // 常量池
    Constant pool:
       #1 = Methodref          #6.#22         // java/lang/Object."<init>":()V
       #2 = InvokeDynamic      #0:#27         // #0:encode:()LEncode;
       #3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
       #4 = Methodref          #30.#31        // java/io/PrintStream.println:(Ljava/lang/Object;)V
       #5 = Class              #32            // MethodReference
       #6 = Class              #33            // java/lang/Object
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               LocalVariableTable
      #12 = Utf8               this
      #13 = Utf8               LMethodReference;
      #14 = Utf8               main
      #15 = Utf8               ([Ljava/lang/String;)V
      #16 = Utf8               args
      #17 = Utf8               [Ljava/lang/String;
      #18 = Utf8               encode
      #19 = Utf8               LEncode;
      #20 = Utf8               SourceFile
      #21 = Utf8               MethodReference.java
      #22 = NameAndType        #7:#8          // "<init>":()V
      #23 = Utf8               BootstrapMethods
      #24 = MethodHandle       #6:#34         // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;L
    java/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang
    /invoke/CallSite;
      #25 = MethodType         #35            //  (LDerive;)V
      #26 = MethodHandle       #5:#36         // invokevirtual Base.encrypt:()V
      #27 = NameAndType        #18:#37        // encode:()LEncode;
      #28 = Class              #38            // java/lang/System
      #29 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;
      #30 = Class              #41            // java/io/PrintStream
      #31 = NameAndType        #42:#43        // println:(Ljava/lang/Object;)V
      #32 = Utf8               MethodReference
      #33 = Utf8               java/lang/Object
      #34 = Methodref          #44.#45        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/Str
    ing;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallS
    ite;
      #35 = Utf8               (LDerive;)V
      #36 = Methodref          #46.#47        // Base.encrypt:()V
      #37 = Utf8               ()LEncode;
      #38 = Utf8               java/lang/System
      #39 = Utf8               out
      #40 = Utf8               Ljava/io/PrintStream;
      #41 = Utf8               java/io/PrintStream
      #42 = Utf8               println
      #43 = Utf8               (Ljava/lang/Object;)V
      #44 = Class              #48            // java/lang/invoke/LambdaMetafactory
      #45 = NameAndType        #49:#53        // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Lj
    ava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      #46 = Class              #54            // Base
      #47 = NameAndType        #55:#8         // encrypt:()V
      #48 = Utf8               java/lang/invoke/LambdaMetafactory
      #49 = Utf8               metafactory
    
    // 字节码指令
     public static void main(java.lang.String[]);
         0: invokedynamic #2,  0              // InvokeDynamic #0:encode:()LEncode;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        13: return
    
    // 属性
    SourceFile: "MethodReference.java"
    InnerClasses:
         public static final #51= #50 of #56; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
    BootstrapMethods:
      0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
    Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
        Method arguments:
          #25 (LDerive;)V
          #26 invokevirtual Base.encrypt:()V
          #25 (LDerive;)V
    

    那么invokedynamic到底是怎么生成encode对象的呢?
    虚拟机解析
    hotspot 对invokedynamic指令的解释如下:

    CASE(_invokedynamic): {
    
            u4 index = Bytes::get_native_u4(pc+1);
            ConstantPoolCacheEntry* cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
    
            // We are resolved if the resolved_references field contains a non-null object (CallSite, etc.)
            // This kind of CP cache entry does not need to match the flags byte, because
            // there is a 1-1 relation between bytecode type and CP entry type.
            if (! cache->is_resolved((Bytecodes::Code) opcode)) {
              CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
                      handle_exception);
              cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
            }
    
            Method* method = cache->f1_as_method();
            if (VerifyOops) method->verify();
    
            if (cache->has_appendix()) {
              ConstantPool* constants = METHOD->constants();
              SET_STACK_OBJECT(cache->appendix_if_resolved(constants), 0);
              MORE_STACK(1);
            }
    
            istate->set_msg(call_method);
            istate->set_callee(method);
            istate->set_callee_entry_point(method->from_interpreted_entry());
            istate->set_bcp_advance(5);
    
            // Invokedynamic has got a call counter, just like an invokestatic -> increment!
            BI_PROFILE_UPDATE_CALL();
    
            UPDATE_PC_AND_RETURN(0); // I'll be back...
          }
    

    使用invokedynamic_cp_cache_entry_at获取常量池对象,然后检查是否已经解析过,如果没有就解析反之复用,然后设置方法字节码,留待后面解释执行。那么,重点是这个解析。
    根据jvm文档的描述,invokedynamic的操作数(operand)指向常量池一个动态调用点描述符(dynamic call site specifier)。
    动态调用点描述符是一个CONSTANT_InvokeDynamic_info结构体:

    CONSTANT_InvokeDynamic_info {
     u1 tag;
     u2 bootstrap_method_attr_index;
     u2 name_and_type_index;
    }
    

    tag 表示这个结构体的常量,不用管
    bootstrap_method_attr_index 启动方法数组
    name_and_type_index 一个名字+类型的描述字段,就像这样Object p放到虚拟机里面表示是Ljava/lang/Object; p

    然后启动方法数组结构是这样:

    BootstrapMethods_attribute {
     ...
     u2 num_bootstrap_methods;
     { 
        u2 bootstrap_method_ref;
        u2 num_bootstrap_arguments;
        u2 bootstrap_arguments[num_boot]
        } bootstrap_methods[num_bootstrap_methods];
    }
    

    就是一个数组,每个元素是{指向MethodHandle的索引,启动方法参数个数,启动方法参数}

    MethodlHandle是个非常重要的结构,指导了虚拟机对于这个启动方法的解析,先关注一下这个结构:

    CONSTANT_MethodHandle_info {
     u1 tag;//表示该结构体的常量tag,可以忽略
     u1 reference_kind;
     u2 reference_index;
    }
    

    reference_kind是[1,9]的数,它表示这个method handle的类型,这个字段和字节码的行为有关。
    reference_index 根据reference_kind会指向常量池的不同类型,具体来说

    eference_kind==1,3,4 指向CONSTANT_Fieldref_info结构,表示一个类的字段
    reference_kind==5,8,指向CONSTANT_Methodref_info,表示一个类的方法
    reference_kind==6,7, 同上,只是兼具接口的方法或者类的方法的可能。
    reference_kind==9,指向CONSTATN_InterfaceMethodref_info,表示一个接口方法
    

    通过invokedynamic,我们可以得

    名字+描述符的表示(由name_and_type_index给出)
    一个启动方法数组(由bootstrap_method_attr_index给出)
    

    手动解析

    可以手动模拟一下解析,看看最后得到的数据是什么样的。在这个例子中:

      0: invokedynamic #2,  0   //第二个operand总是0
    

    查看常量池#2项:

    #2 = InvokeDynamic      #0:#27         // #0:encode:()LEncode;
    #27 = NameAndType        #18:#37        // encode:()LEncode;
    
    BootstrapMethods:
      0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
    Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
        Method arguments:
          #25 (LDerive;)V
          #26 invokevirtual Base.encrypt:()V
          #25 (LDerive;)V
    

    得到的名字+描述符是:Encode.encode(),启动方法数组有一个元素,回忆下之前说的,这个元素构成如下:

    {指向MethodHandle的索引,启动方法参数个数,启动方法参数}
    
    

    这里得到的MethodHandle表示的是LambdaMetafactory.metafactory:

    #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
    Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;`
    

    启动方法参数有:

    #25 (LDerive;)V
    #26 invokevirtual Base.encrypt:()V
    #25 (LDerive;)V
    
    

    java.lang.invoke.LambdaMetafactory

    先说说LambdaMetafactory有什么用。javadoc给出的解释是:

    Facilitates the creation of simple "function objects" that implement one or more interfaces by delegation to a provided MethodHandle, after appropriate type adaptation and partial evaluation of arguments. Typically used as a bootstrap method for invokedynamic call sites, to support the lambda expression and method reference expression features of the Java Programming Language.
    When the target of the CallSite returned from this method is invoked, the resulting function objects are instances of a class which implements the interface named by the return type of invokedType, declares a method with the name given by invokedName and the signature given by samMethodType. It may also override additional methods from Object.

    LambdaMetafactory方便我们创建简单的"函数对象",这些函数对象通过代理MethodHandle实现了一些接口。
    当这个函数返回的CallSite被调用的时候,会产生一个类的实例,该类还实现了一些方法,具体由参数给出

    将上面得到的MethodHandle写得更可读就是调用的这个方法:

     public static CallSite LambdaMetafactory.metafactory(MethodHandles.Lookup caller,
                                           String invokedName,
                                           MethodType invokedType,
                                           MethodType samMethodType,
                                           MethodHandle implMethod,
                                           MethodType instantiatedMethodType);
    

    六个参数,慢慢来。

    LambdaMetafactory.metafactory()调用前

    要知道参数是什么意思,可以从它的调用者来探索:

     static CallSite makeSite(MethodHandle bootstrapMethod,
                                 // Callee information:
                                 String name, MethodType type,
                                 // Extra arguments for BSM, if any:
                                 Object info,
                                 // Caller information:
                                 Class<?> callerClass) {
            MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
            CallSite site;
            try {
                Object binding;
                info = maybeReBox(info);
                if (info == null) {
                    binding = bootstrapMethod.invoke(caller, name, type);
                } else if (!info.getClass().isArray()) {
                    binding = bootstrapMethod.invoke(caller, name, type, info);
                } else {
                    Object[] argv = (Object[]) info;
                    maybeReBoxElements(argv);
                    switch (argv.length) {
                    ...
                    case 3:
                        binding = bootstrapMethod.invoke(caller, name, type,
                                                         argv[0], argv[1], argv[2]);
                        break;
                    ...
                    }
                }
                //System.out.println("BSM for "+name+type+" => "+binding);
                if (binding instanceof CallSite) {
                    site = (CallSite) binding;
                }  else {
                    throw new ClassCastException("bootstrap method failed to produce a CallSite");
                }
                ...
            } catch (Throwable ex) {
                ...
            }
            return site;
        }
    

    对java.lang.invoke.LambdaMetafactory的调用是通过MethodHandle引发的,所以可能还需要补一下MethodHandle的用法,javadoc也给出了使用示例:

    String s;
    MethodType mt; MethodHandle mh;
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    // mt is (char,char)String
    mt = MethodType.methodType(String.class, char.class, char.class);
    mh = lookup.findVirtual(String.class, "replace", mt);
    s = (String) mh.invoke("daddy",'d','n');
    // invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
    assertEquals(s, "nanny");
    

    回到源码,关键是这句:

    binding = bootstrapMethod.invoke(caller, name, type,
                                   argv[0], argv[1], argv[2]);
    
    

    argv[0],argv[1],argv[2]分别表示之前启动方法的三个参数,
    caller即调用者,这里是MethodReference这个类,然后name和type参见下面的详细解释:

    MethodHandles.Lookup caller 表示哪个类引发了调动
    String invokedName 表示生成的类的方法名,对应例子的encode
    MethodType invokedType 表示CallSite的函数签名,其中参数类型表示捕获变量的类型,返回类型是类要实现的接口的名字,对应例子的()Encode,即要生成一个类,这个类没有捕获自由变量(所以参数类为空),然后这个类要实现Encode接口(返回类型为生成的类要实现的接口)
    MethodType samMethodType 表示要实现的方法的函数签名和返回值,对于例子的#25 (LDerive;)V,即实现方法带有一个形参,返回void
    MethodHandle implMethod 表示实现的方法里面应该调用的函数,对于例子的#26 invokevirtual Base.encrypt:()V,表示调用Base的虚函数encrypt,返回void
    MethodType instantiatedMethodType 表示调用方法的运行时描述符,如果不是泛型就和samMethodType一样
    

    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();
        }
    

    它什么也没做,做事的是InnerClassLambdaMetafactory.buildCallSite()创建的最后CallSite,

      @Override
        CallSite buildCallSite() throws LambdaConversionException {
            // 1. 创建生成的类对象
            final Class<?> innerClass = spinInnerClass();
            if (invokedType.parameterCount() == 0) {
                // 2. 用反射获取构造函数
                final Constructor<?>[] ctrs = AccessController.doPrivileged(
                        new PrivilegedAction<Constructor<?>[]>() {
                    @Override
                    public Constructor<?>[] run() {
                        Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
                        if (ctrs.length == 1) {
                            // The lambda implementing inner class constructor is private, set
                            // it accessible (by us) before creating the constant sole instance
                            ctrs[0].setAccessible(true);
                        }
                        return ctrs;
                    }
                        });
                if (ctrs.length != 1) {
                    throw new LambdaConversionException("Expected one lambda constructor for "
                            + innerClass.getCanonicalName() + ", got " + ctrs.length);
                }
    
                try {
                    // 3. 创建实例 
                    Object inst = ctrs[0].newInstance();
                    // 4. 根据实例和samBase(接口类型)生成MethodHandle
                    // 5. 生成ConstantCallSite
                    return new ConstantCallSite(MethodHandles.constant(samBase, inst));
                }
                catch (ReflectiveOperationException e) {
                    throw new LambdaConversionException("Exception instantiating lambda object", e);
                }
            } else {
                try {
                    UNSAFE.ensureClassInitialized(innerClass);
                    return new ConstantCallSite(
                            MethodHandles.Lookup.IMPL_LOOKUP
                                 .findStatic(innerClass, NAME_FACTORY, invokedType));
                }
                catch (ReflectiveOperationException e) {
                    throw new LambdaConversionException("Exception finding constructor", e);
                }
            }
        }
    

    首先它生成一个.class文件,虚拟机默认不会输出,需要下面设置VM option-Djdk.internal.lambda.dumpProxyClasses=.,Dump出虚拟机生成的类我得到的是:

    import java.lang.invoke.LambdaForm.Hidden;
    
    // $FF: synthetic class
    final class MethodReference$$Lambda$1 implements Encode {
        private MethodReference$$Lambda$1() {
        }
    
        @Hidden
        public void encode(Derive var1) {
            ((Base)var1).encrypt();
        }
    }
    
    

    该类实现了传来的接口函数(动态类生成).
    回到buildCallSite()源码,它使用MethodHandles.constant(samBase, inst)创建MethdHandle,放到CallSite里面,完成整个LambdaMetafactory的工作。 MethodHandles.constant(samBase, inst)相当于一个总是返回inst的方法。

    小结

    1. 虚拟机遇到invokedynamic,开始解析操作数
    2. 根据invokedynamic #0:#27获取到启动方法(#0)和一个名字+描述符(#27)
      其中启动方法是
    BootstrapMethods:
      0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
    Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
        Method arguments:
          #25 (LDerive;)V
          #26 invokevirtual Base.encrypt:()V
          #25 (LDerive;)V
    
    1. 名字+描述符是
     #27 = NameAndType        #18:#37        // encode:()LEncode;
    
    1. 启动方法指向LambdaMetafactory.metafactory,但是不会直接调用而是通过MethdHandle间接调用。调用位置位于CallSite.makeCallSite()

    2. LambdaMetafactory.metafactory()其实使用InnerClassLambdaMetafactory.buildCallSite()创建了最后的CallSite

    3. buildCallSite()会创建一个.class,

    4. buildCallSite()会向最后的CallSite里面放入一个可调用的MethdHandle

    5. 这个MethodHandle指向的是一个总是返回刚刚创建的.class类的实例的方法,由MethodHandles.constant(samBase, inst)完成

    6. 最后,用invokevirtual调用CallSite里面的MethdHandle,返回.class类的示例,即inst,即new MethodReference$$Lambda$1

    相关文章

      网友评论

        本文标题:深入理解Lambda表达式

        本文链接:https://www.haomeiwen.com/subject/ishphctx.html