首页 > 代码库 > Java8 Lambda表达式深入学习(4) -- Java8实现方式
Java8 Lambda表达式深入学习(4) -- Java8实现方式
前几篇文章讨论了函数式接口和Lambda表达式语法、invokedynamic指令,以及Groovy2如何利用indy指令。本篇文章在前面几篇的基础之上,简要介绍Java8底层是如何实现Lambda表达式的。
示例代码
本文将以下面的代码为例展开讨论:
import java.util.Arrays; import java.util.List; public class LambdaImplTest { public static void main(String[] args) { m1(Arrays.asList(args)); } public static void m1(List<String> list) { list.sort((a, b) -> a.length() - b.length()); } }
插入invokedynamic指令
直接利用匿名类来实现Lambda表达式肯定也是可行的,这样,Lambda表达式就只是Java编译器的语法糖而已。但是Java8并没有这样做,而是使用了更复杂(也更灵活)的方式:利用indy指令。显然,这种方式需要编译器和JVM一同配合来实现。编译器会在每个Lambda表达式出现的地方插入一条indy指令,同时还必须在class文件里生成相应的CONSTANT_InvokeDynamic_info常量池项和BootstrapMethods属性等信息。这些信息将这条indy指令的bootstrap方法指向LambdaMetafactory.metafactory(...)方法。
插入lambda$x&y方法
用javac编译LambdaImplTest.java,然后用javap -v -p反编译.class文件,可以看到,编译器生成了一个lambda$m1$0方法,并且将Lambda表达式的内容搬到了里面:
编译器会按照一定的规则来给Lambda表达式生成方法,以保证这些方法不会重名。如果把字节码还原成Java代码的话,LambdaImplTest看起来会像下面这样:
public class LambdaImplTest { public static void main(String[] args) { m1(Arrays.asList(args)); } public static void m1(List<String> list) { list.sort(/*lambda*/); } private static int lambda$m1$0(String a, String b) { return a.length() - b.length(); } }
LambdaMetafactory.metafactory(...)方法
当JVM第一次执行到这条indy指令的时候,它会找到这条指令相应的bootstrap方法,然后调用该方法,得到一个CallSite。下面是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(); }从代码可以猜到,Java8内部也是以内部类的方式来实现Lambda表达式的。而InnerClassLambdaMetafactory的buildCallSite()方法证明了这一点,buildCallSite()方法太长,这里就不贴代码了,总之它会调用一个叫做spinInnerClass()的方法,正是这个方法使用字节码工具在内存中生成了一个类。
观察spinInnerClass()生成的类
如果我们在启动JVM的时候设置系统属性"jdk.internal.lambda.dumpProxyClasses"的话,spinnerClass()方法就会将生成的类保存到文件中,以方便调试。如果我们用下面的命令运行LambdaImplTest,就能在“当前目录”看到这个类:
java -Djdk.internal.lambda.dumpProxyClasses LambdaImplTest
LambdaImplTest$$Lambda$1.class同样可以使用javap命令来反编译这个class文件,下面是反编译结果(我自己把javap结果转成了java文件):
final class LambdaImplTest$$Lambda$1 implements java.util.Comparator { private LambdaImplTest$$Lambda$1() { } public int compare(Object a, Object b) { return LambdaImplTest.lambda$m1$0:((String) a, (String) b); } }可见这个内部类实现了Comparator接口,compare()方法只是调用lambda$m1$0()方法而已。继续分析buildCallSite()方法可知,JVM接着实例化了这个内部类的一个实例,然后创建了一个ConstantCallSite,它的目标MethodHandle指向内部类实例的compare()方法。
示意图
文字描述很难说清楚问题,下面我画了一张示意图:
总结
上面分析了最简单的Lambda表达式的Java内部实现,如果Lambda表达式捕获了外部参数的话,情况会稍微复杂一点。不过结论仍然不变:Java8底层也是以内部类的方式来实现Lambda表达式的。