Lambda表达式是Java 8发布的新特性,主要是用于简化匿名内部类作为参数传递的写法,省去了一些模版代码,让代码更加简洁,Lambda表达式有以下几个特点
虽然Lambda表达式和匿名内部类关系密切,但实际上它和匿名内部类还是不同的,比如
我们知道Java中只有两种类型,基本类型和引用类型,Lambda表达式很明显不是基本类型,那么Lambda表达式是如何转化为引用类型的呢?下面以一个简单的示例,看看Lambda表达式最终是如何转化并实现调用的。
文件 | 大小 | 修改时间 |
---|---|---|
Main.java | 295 B | 2021年08月18日 |
字节码.txt | 1009 B | 2021年08月18日 |
字节码完整版.txt | 6 kB | 2021年08月18日 |
查看字节码.txt
文件27行,可以看出多了一个方法lambda$main$0(int, int)
,它的参数列表类型和返回类型和Lambda表达式是一致的,可以推断Lambda表达式编译成了一个私有静态方法,虽然编译成了一个私有静态方法,但是字节码中并没有地方直接调用它,原本调用Lambda表达式的地方只是调用了invokedynamic指令,所以猜测invokedynamic指令最终调用了lambda$main$0(int, int)
。
invokedynamic是Java字节码中的一个方法调用指令,它和其它方法调用指令不同的是它调用的方法并不是在编译时确定好的,而是通过一个叫bootstrap method的方法来获取的,而bootstrap method方法具体返回什么是由我们自己实现的。Lambda表达式对应的bootstrap method定义在java.lang.invoke.LambdaMetafactory.metafactory(...)
,本示例的invokedynamic解析执行流程大致如下
字节码完整版.txt第86行,执行invokedynamic指令
0: invokedynamic #2, 0 #2表示调用信息在常量池的索引
字节码完整版.txt第11行,在常量池#2获取动态调用需要的参数
#2 = InvokeDynamic #0:#29 #0表示在BootstrapMethods的索引,#29表示方法和类型在常量池中的索引
字节码完整版.txt第38,50,51行,在常量池中找到类型和方法
#29 = NameAndType #41:#42
#41 = Utf8 call
#42 = Utf8 ()LMain$FunInterface;
字节码完整版.txt第117行,在BootstrapMethods#0里面找到对应的bootstrap method
0: #26 invokestatic java/lang/invoke/LambdaMetafactory.metafactory(...)...
Method arguments:
#27 (II)I
#28 invokestatic Main.lambda$main$0:(II)I
#27 (II)I
拿到动态调用所有需要的信息后,虚拟机会调用bootstrap method,也就是LambdaMetafactory.metafactory(...)
,在这个方法里面会生成一个实现FunInterface接口的内部类,并且在这个类的call方法中调用Lambda表达式所生成的私有静态方法lambda$main$0
,最后返回一个CallSite对象,而这个CallSite对象会包含一个MethodHandle,通过调用这个MethodHandle,就可以实例化一个刚才生成的内部类。这样invokedynamic执行完成后就会返回一个实现FunInterface,并且在call方法里面调用Lambda表达式生成的私有静态方法的内部类。
简单来说,invokedynamic指令通过参数找到并调用了LambdaMetafactory.metafactory(...)方法,在运行时创建了一个实现FunInterface接口的内部类,然后通过这个内部类最终调用了lambda$main$0方法。