我们来看两段代码:
lambda 版本:
public class LambdaTest{
public static void main(String[] args) {
Runnable r = ()->{
System.out.println("hello, lambda");
};
r.run();
}
}
匿名内部类版本:
public class LambdaTest2{
public static void main(String[] args) {
Runnable r = new Runnable(){
@Override
public void run(){
System.out.println("hello, lambda");
}
};
r.run();
}
}
在日常开发过程中,其实我们感受不到两者有什么区别,反正最终运行的结果都是一致的。
那么问题来了,这两个写法究竟有什么区别?仅仅在写法上的不同吗?
注意:本题目针对 Java语言。
更多问答 >>
-
每日一问 | RxJava中Observable、Flowable、Single、Maybe 有何区别?
2021-01-03 20:34 -
每日一问 | View invalidate() 相关的一些细节探究~
2020-12-27 22:38 -
每日一问 | Call requires API level 23 (current min is 14) 扫描出来的原理是?
2020-12-27 22:39 -
每日一问 | 当Unsafe遇上final,超神奇的事情发生了?
2020-11-02 00:16 -
每日一问 | 属性动画与硬件加速的相遇,不是你想的那么简单?
2020-10-26 23:45
LambdaTest和LambdaTest2除了写法不同,还有哪些区别?
用匿名内部类方式实现的LambdaTest2,大家都很熟悉了:javap
看一下,确实就是Runnable的实现类。看下main
方法:run
方法,没什么特别。那现在来看看用lambda方式实现的LambdaTest:
javap -v
看下:invokedynamic
出现了,哈哈哈。在invokedynamic
执行完之后,储存栈顶值,入栈,然后调用接口Runnable的run
方法。最主要还是那句invokedynamic
,看highlight了的:InvokeDynamic #0
,后面的#0
对应的并不是常量池里的索引,而是一个叫BootstrapMethods的:0:
后面的内容了,可以看出它是通过invokestatic
来调用LambdaMetafactory的metafactory
方法,下面还有三个参数。不过,LambdaMetafactory的metafactory
方法是有6个参数的,为什么这里只列出了3个呢?因为前3个参数都是由JVM自动处理的:
第一个参数caller
,用来保存声明lambda时所对应的上下文(其实就是lambda所在类)信息,比如题目中的例子,就是LambdaTest.class;invokedName
是接口需要实现的方法的名称,代入题目的代码,这里自然是"run"了;invokedType
,里面记录了lambda所用到的所有本地变量的Class,因为接下来还要把这些本地变量当作参数传进来(等下说);上面字节码列出的,对应的是
metafactory
方法后三个参数:泛型擦除后的方法签名及返回值;
对应的匿名方法信息;
泛型擦除之前的方法签名以及返回值;
1,3都很好理解,就是2调用的方法,怎么指向的是LambdaTest类里的一个奇奇怪怪的方法名?难道编译器又帮我们生成了一个额外的方法吗?看看:
真的耶!多了一个
lambda$main$0
方法,我们试试用反射调用它,看看会怎么样:
"hello, lambda"多打印了一次!!!看下它的字节码:System.out
,"hello, lambda"
入栈,然后调用(System.out的)println
方法。这不就是lambda的内容嘛!不过,既然这里变成了方法,那它在哪里被调用呢?抛个异常看看堆栈信息:
居然没有。。。好叭,只能自己动手了,我们可以借鉴Class.forName
的做法——通过Reflection的getCallerClass
来获取到调用者的Class信息:运行看下:
lambda$main$0
)的居然是一个叫LambdaTest$$Lambda$1/471910020的类,好奇怪啊这个名字,在编译后明明没看到有这个类的,难道它是JVM帮我们动态生成和加载的Runnable实现类???照现在看来,也只能这么理解了。打印一下Runnable实例的类名,在LambdaTest的main
方法里追加:重新编译,运行:
现在可以得出结论:
Lambda表达式在编译后,其函数体都会放到Caller Class里一个额外生成的方法内(所谓的"匿名"函数嘛),由JVM动态生成的实现类去调用。那么,是不是所有的lambda在编译后都会生成额外的方法呢?
不是的,lambda除了params -> {};
这种大括号的形式,还有另外一种叫Method Reference(方法引用)的,相信大家也见得多了,就像这样:object::method;
,这种形式的lambda它是不会生成额外方法的。感兴趣的同学可以去对比下它们的字节码,一定要多动手印象才能更深刻(就像小时候不听话被爸妈打的,现在还记得咧)回到题目中的问题:
Java中的lambda,真的是语法糖吗?emmmmm,我认为它是语法糖,因为最终也是通过内部类的方式实现的,只是不像直接使用内部类那样在编译后生成额外的类,它是把生成实现类这一步放到了运行时,由JVM去完成。看跪了
真大佬!最近结论中【把生成实现类这一步放到了运行时,由JVM去完成】这个会影响性能吗
emmmm,理论上是会的,但主要耗时不是在生成实现类的过程(因为这个实现类没多少代码,lambda的主体在编译时就已经放到callerClass的synthetic方法里,实现类唯一要做的事就是调用这 ...查看更多
emmmm,理论上是会的,但主要耗时不是在生成实现类的过程(因为这个实现类没多少代码,lambda的主体在编译时就已经放到callerClass的synthetic方法里,实现类唯一要做的事就是调用这个synthetic方法),而是在生成实现类之前所做的准备工作,我搜了一下,JVM在生成lambda实现类的时候也是借助ASM来完成的!!!也就是它在生成实现类之前,还要去load ASM的jar!不过,这点耗时相比于它带来的便捷性,我觉得完全可以忽略不计。
很到位,完全匹配了我预期的回答。
哈哈哈哈哈
感谢感谢
我也不知道怎么回答,只是看到字节码中有 LambdaMetafactory.metafactory进行对lambda的各种处理,其他的区别看不出来
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;
图片太长了,不方便截取
看了下代码是这样的。
剩余的,就是看metafactory方法了。https://github.com/ff-frida/temp_image_repo/blob/master/java/QQ%E6%88%AA%E5%9B%BE20210104095026.png ...查看更多
https://github.com/ff-frida/temp_image_repo/blob/master/java/QQ%E6%88%AA%E5%9B%BE20210104095026.png
帮你更新了一下图片排版。
你的这个插件叫啥啊?
谢谢
Class Decompile QuDing https://plugins.jetbrains.com/plugin/13914-class-decompile ...查看更多
Class Decompile QuDing https://plugins.jetbrains.com/plugin/13914-class-decompile