深入 java 8 lambda,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 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表达式的内容搬到了里面:

5ef7e5bd417c14cc81bd745c8de0d2b8.png

编译器会按照一定的规则来给Lambda表达式生成方法,以保证这些方法不会重名。如果把字节码还原成Java代码的话,LambdaImplTest看起来会像下面这样:

public class LambdaImplTest {

public static void main(String[] args) {

m1(Arrays.asList(args));

}

public static void m1(List 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 LambdaImplTestLambdaImplTest$$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()方法。

示意图

文字描述很难说清楚问题,下面我画了一张示意图:

88ebc0759e9c609db7f8764083a77c9e.png

总结

上面分析了最简单的Lambda表达式的Java内部实现,如果Lambda表达式捕获了外部参数的话,情况会稍微复杂一点。不过结论仍然不变:Java8底层也是以内部类的方式来实现Lambda表达式的。

原文:http://blog.csdn.net/zxhoo/article/details/38495085

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值