java8 lambda学习笔记之编译与运行过程

从最简单的lambda说起LambdaTest.java,代码如下:

package lambda.demo;

import java.util.function.Supplier;

public class LambdaTest {
    public static void main(String[] args) {
        Supplier supplier = () -> 1;
        System.out.println(supplier.get());
    }
}
构建了一个lambda表达式赋值给Supplier函数接口,然后打印Supplier对象提供的值。

编译这个类,并用javap查看生成的字节码,部分字节码如下:

public class lambda.demo.LambdaTest
  SourceFile: "LambdaTest.java"
  InnerClasses:
       public static final #66= #65 of #69; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
  BootstrapMethods:
    0: #30 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:
        #31 ()Ljava/lang/Object;
        #32 invokestatic lambda/demo/LambdaTest.lambda$main$0:()Ljava/lang/Integer;
        #33 ()Ljava/lang/Integer;
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#28         //  java/lang/Object."":()V
   #2 = InvokeDynamic      #0:#34         //  #0:get:()Ljava/util/function/Supplier;
  .......
  #27 = Utf8               LambdaTest.java
  #28 = NameAndType        #9:#10         //  "":()V
  #29 = Utf8               BootstrapMethods
  #30 = MethodHandle       #6:#45         //  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;
  #31 = MethodType         #46            //  ()Ljava/lang/Object;
  #32 = MethodHandle       #6:#47         //  invokestatic lambda/demo/LambdaTest.lambda$main$0:()Ljava/lang/Integer;
  #33 = MethodType         #25            //  ()Ljava/lang/Integer;
  ......
{
  ......
  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:get:()Ljava/util/function/Supplier;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: invokeinterface #4,  1            // InterfaceMethod java/util/function/Supplier.get:()Ljava/lang/Object;
        15: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        18: return
  ......		
}
可以看到第32行出现了invokedynamic指令,这正是lambda表达式在编译的时候生成的,顺着常量池索引#2找到CONSTANT_InvokeDynamic_info,由两部分组成:

(1)bootstrap方法限定符

每个invokedynamic指令出现的地方被称为动态调用点,每个动态调用点都对应一个bootstrap方法,bootstrap方法返回值为CallSite,此例中bootstrap方法为java.lang.invoke.LambdaMetafactory类的静态方法metafactory。

在方法限定符中还有三个参数分别是:

#31 ()Ljava/lang/Object; 表示函数式接口Supplier.get方法描述符
#32 invokestatic lambda/demo/LambdaTest.lambda$main$0:()Ljava/lang/Integer; lambda表达式编译时生成的匿名静态方法对应的描述符,这个匿名方法具体情况如下:

所在类:lambda.demo.LambdaTest

方法名:lambda$main$0

参数:无

返回值:java.lang.Integer
#33 ()Ljava/lang/Integer;具体的方法描述符,与#31相对应

(2)方法名和方法描述符:此处为Supplier get().


在这里可以看出:lambda在编译的时候生成匿名静态方法,同时还生成了invokedynamic指令,其bootstrap方法对应的动态调用点的MethodHandle,底层方法就是这个匿名方法。


当我们运行main函数时,JVM会将这些参数出栈,并传入LambdaMetafactory.metafactory方法中,以下是metafactory方法的定义:

public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
此处caller为lambda.demo.LambdaTest类

invokedName为get

invokedType为()Supplier

后面三个参数分别为方法限定符中的三个静态参数。

真正创建动态调用点的地方在InnerClassLambdaMetafactory.buildCallSite方法中,主要做了三件事:

(1)通过ASM工具生成名为lambda.demo.LambdaTest$$Lambda$1的类的字节码;注意ForwardingMethodGenerator.generate方法中以下代码:

visitMethodInsn(invocationOpcode(), implMethodClassName,
                            implMethodName, implMethodDesc,
                            implDefiningClass.isInterface());
是将编译时生成的匿名方法对应的字节码加入到新生成的类中

(2)为lambda.demo.LambdaTest$$Lambda$1类创建实例,并转换为Supplier对应的MethodHandle;

(3)将(2)中的MethodHandler做为target生成动态调用点,并返回。


在这里可以看出:运行时通过ASM工具生成新类的字节码,包含了编译时的匿名方法,新类最终会被包装成一个动态调用点。执行invokedynamic指令,就会执行动态调用点的bootstrap方法,其底层方法就是编译时的匿名方法,也就是执行lambda表达式中的代码。


新生成的类的命名规则为:包名.类名$$Lambda$自增计数,当我们在debug或者抛出异常时看到这种类名就可以理解了:

Exception in thread "main" java.lang.RuntimeException
	at lambda.demo.LambdaTest.lambda$main$0(LambdaTest.java:13)
	at lambda.demo.LambdaTest$$Lambda$1/1283928880.accept(Unknown Source)
	at lambda.demo.LambdaTest.main(LambdaTest.java:15)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

你可能感兴趣的:(JAVA基础)