JDK8 的 Lambda 表达式原理
JDK8 使用一行 Lambda 表达式可以代替先前用匿名类五六行代码所做的事情,那么它是怎么实现的呢?从所周知,匿名类会在编译的时候生成与宿主类带上 $1, $2 的类文件,如写在 TestLambda 中的匿名类产生成类文件是 TestLambda$1.class, TestLambda$2.class 等。
我试验了一下,如果使用的是 Lambda 表达式并不会生成额外的类文件,那么字节码里是什么样子的?来看下用 javap -c 反编译出下面文件产生的 TestLambda.class,两个方法,一个是 byAnonymousClass() 使用匿名类,另一个是 byLambda 使用 Lambda 的方式:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package cc.unmi.testjdk8; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; public class TestLambda{ private JButton button = new JButton(); public void byLambda() { button.addActionListener((ActionEvent e) -> System.out.println( "Lambda" )); } public void byAnonymousClass(){ button.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println( "Anonymous class" ); } }); } } |
相应的字节码如下:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class cc.unmi.testjdk8.TestLambda { public cc.unmi.testjdk8.TestLambda(); Code: 0 : aload_0 1 : invokespecial # 10 // Method java/lang/Object."<init>":()V 4 : aload_0 5 : new # 12 // class javax/swing/JButton 8 : dup 9 : invokespecial # 14 // Method javax/swing/JButton."<init>":()V 12 : putfield # 15 // Field button:Ljavax/swing/JButton; 15 : return public void byLambda(); Code: 0 : aload_0 1 : getfield # 15 // Field button:Ljavax/swing/JButton; 4 : invokedynamic # 25 , 0 // InvokeDynamic #0:actionPerformed:()Ljava/awt/event/ActionListener; 9 : invokevirtual # 26 // Method javax/swing/JButton.addActionListener:(Ljava/awt/event/ActionListener;)V 12 : return public void byAnonymousClass(); Code: 0 : aload_0 1 : getfield # 15 // Field button:Ljavax/swing/JButton; 4 : new # 31 // class cc/unmi/testjdk8/TestLambda$1 7 : dup 8 : aload_0 9 : invokespecial # 33 // Method cc/unmi/testjdk8/TestLambda$1."<init>":(Lcc/unmi/testjdk8/TestLambda;)V 12 : invokevirtual # 26 // Method javax/swing/JButton.addActionListener:(Ljava/awt/event/ActionListener;)V 15 : return } |
对比后我们发现,匿名类的方式会创建一个匿名类(这是废话),如编译出的的 TestLambda$1.class 文件,在磁盘上能看到 TestLambda$1.class 文件
而 Lambda 的方式则不会产生额外的类文件,我们可以让 TestLambda 只保留 byLambda() 方法,就会发现编译后只会有 TestLambda.class 文件。
对比方法调用指令,byLambda 中使用了一个 JDK7 新加的 invokedynamic
虚拟机指令。invokedynamic 就是个关键,这里不去深挖,只简单说明,总之它对于 Java 进行函数式编程,增强了语言的动态性意义匪浅,它重新引入了像 C 里函数指针类似的方法句柄的概念。JDK7 之前的方法调用指令有 invokestatic, invokespecial, invokevirtual 和 invokeinterface 四个,它们都是在编译时就确定了实际调用哪个方法。而 invokedynamic 能让虚拟机在执行到该指令时才去动态的链接,调用实际的方法,所以每个 invokedynamic 就是一个动态调用点。
具体到我们的例子,也就是说对于这个例子虚拟机会在执行 byLambda 的 invokedynamic #25, 0 指令时动态的在内存中创建一个类似与 TestLambda$1 的类,名字可能是 TestLambda$1$$Lambda$1。
为此,我专门做了个实验,下面的代码编译会生成两个类文件,TestLambda.class,TestLambda$1.class, 第二个类文件是由 new ActionListener() 时创建的匿名类。 然后执行下面的代码:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package cc.unmi.testjdk8; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; public class TestLambda{ private static final JButton button = new JButton( "Click" ); public static void main(String[] args) { JFrame frame = new JFrame(); frame.setSize( 600 , 480 ); frame.add(button); button.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { button.addActionListener((ActionEvent ae) -> { System.out.println( "button clicked." ); }); } }); frame.setVisible( true ); } } |
JDK 给我们提供了不少分析 JVM 的工具,如 jps, jinfo, jstack, jmap, jhat, jconsole, jvisualvm 等。
命令:
jps #可看到 Java Process ID
jmap -dump:file=before_click.dump <pid> #click 前堆转储为文件
jmap -dump:file=after_click.dump <pid> #click 后堆转储为文件
jhat before_click.dump #默认在 7000 端口打开 Web 服务浏览 open http://localhost:7000
jhat -port 7001 after_click.dump #open http://localhost:7001
我们可分别在点击按钮的前后用 jmap 生成快照文件 before_click.dump 和 after_click.dump。在点击按钮之前虚拟机还未真正执行到 Lambda 表达式。
用 jhat 浏览 before_click.dump,即点击按钮之前的快照,看到的是:
只加载了两个类,TestLambda 和匿名类 TestLambda$1。点击 class cc.unmi.testjdk8.TestLambda$1, 看到 TestLambda$1 中是说继承自 Object, 并未告知它与 ActionListner 有何实现上的关系。
再用 jhat 打开点击按钮执行了 Lambda 表达式后的快照 after_click.dump,这时候就会发现多一个类 cc.unmi.testjdk8.TestLambda$1$$Lambda$1
在磁盘上并不存在这个文件,这是在执行 Lambda 表达式时内存中动态生成的,点击它看看它的父类是什么
在 JDK8 正式版中,它的父类变成了 java.lang.Object。
在早先的 JDK8 Beta 版中,它的父类是 MagicLambdaImpl,确实有点名符其实,Magic,以图为证:
上面使用的 JDK 工具是 jmap 和 jhat,也可以用 Java VisualVM -- 即 jvisualvm 指令来查看执行 lambda 前后的内存快照。截了两个图:
上图为点击按钮前的快照,可以看到只有两个类
上图是点击按钮后的快照,又多了 cc.unmi.testjdk8.TestLambda$1$$Lambda$1 这个类。
小结:JDK8 在实现 Lambda 时使用了 JDK7 虚拟机开始有的 invokedynamic 方法调用指令,该知使得虚拟机执行到 Lambda 表达式时才动态的去创建相应的实现类,并加载执行。
JDK8 的 Lambda 表达式原理的更多相关文章
- Java8 Lambda表达式原理扫盲
背景 在使用Lamdba表达式,一直以为是内部类的方式实现的,但是一想如果每次调用都实例化一个内部类,性能肯定不好,难道Java里的lambda表达式真的是这么实现的吗?也许是该研究下原理了. 正文 ...
- Java8新特性(一)——Lambda表达式与函数式接口
一.Java8新特性概述 1.Lambda 表达式 2. 函数式接口 3. 方法引用与构造器引用 4. Stream API 5. 接口中的默认方法与静态方法 6. 新时间日期 API 7. 其他新特 ...
- java8学习之Lambda表达式初步与函数式接口
对于Java8其实相比之前的的版本增加的内容是相当多的,其中有相当一大块的内容是关于Lambda表达式与Stream API,而这两部分是紧密结合而不能将其拆开来对待的,但是是可以单独使用的,所以从学 ...
- jdk8 Lambda表达式与匿名内部类比较
Labmda表达式与匿名内部类 前言 Java Labmda表达式的一个重要用法是简化某些匿名内部类(Anonymous Classes)的写法.实际上Lambda表达式并不仅仅是匿名内部类的语法糖, ...
- jdk8新特性-亮瞎眼的lambda表达式
jdk8之前,尤其是在写GUI程序的事件监听的时候,各种的匿名内部类,大把大把拖沓的代码,程序毫无美感可言!既然Java中一切皆为对象,那么,就类似于某些动态语言一样,函数也可以当成是对象啊!代码块也 ...
- Lambda表达式运行原理
目录 一.创建测试样例 二.利用Java命令编译分析 三.文末 JDK8引入了Lambda表达式以后,对我们写代码提供了很大的便利,那么Lambda表达式是如何运用简单表示来达到运行效果的呢?今天,我 ...
- jdk8的新特性 Lambda表达式
很多同学一开始接触Java8可能对Java8 Lambda表达式有点陌生. //这是一个普通的集合 List<Employee> list = em.selectEmployeeByLog ...
- JDK8的新特性——Lambda表达式
JDK8已经发布快4年的时间了,现在来谈它的新特性显得略微的有点“不合时宜”.尽管JDK8已不再“新”,但它的重要特性之一——Lambda表达式依然是不被大部分开发者所熟练运用,甚至不被开发者所熟知. ...
- c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)
c#封装DBHelper类 public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...
随机推荐
- C#中的IEnumable与IEnumator接口的简单理解
IEnumerable接口中的方法是返回IEnumator的对象,集合继承了IEnumerator接口才能实现Foreach方法实现遍历.集合类都继承IEnumable和IEnumerator接口,或 ...
- 如何参与一个 GitHub 开源项目?
最近一年开源项目特别的热,很多技术大会或论坛都以开源项目作为主题进行探讨,可见这是一种趋势.而Github作为开源项目的著名托管地,可谓无 人不知,越来越多的个人和公司纷纷加入到Github的大家族里 ...
- 【转】 Nginx深入详解之多进程网络模型
[转自]http://blog.chinaunix.net/uid-22312037-id-3974068.html 一.进程模型 Nginx之所以为广大码农喜爱,除了其高性能外,还有其 ...
- AMD加载器实现笔记(五)
前几篇文章对AMD规范中的config属性几乎全部支持了,这一节主要是进一步完善.到目前为止我们的加载器还无法处理环形依赖的问题,这一节就是解决环形依赖. 所谓环形依赖,指的是模块A的所有依赖项的依赖 ...
- WCF基础教程之异常处理:你的Try..Catch语句真的能捕获到异常吗?
在上一篇WCF基础教程之开篇:创建.测试和调用WCF博客中,我们简单的介绍了如何创建一个WCF服务并调用这个服务.其实,上一篇博客主要是为了今天这篇博客做铺垫,考虑到网上大多数WCF教程都是从基础讲起 ...
- [SQLServer大对象]——FileTable初体验
阅读导航 启用FILESTREAM设置 更改FILESTRAM设置 启用数据库非事务性访问级别 FileTable 在我接触FileTable之前,存储文件都是存储文件的链接和扩展名到数据,其实并没有 ...
- 如何用django开发一个简易个人Blog
功能概要:(目前已实现功能) 公共展示部分: 1.网站首页展示已发布的博客记录,包括名称.摘要信息.发布日期.阅读量及评论数. 2.首页文章列表可按照分类筛选. 3.点击标题或阅读全文链接,进入博客阅 ...
- WebViewJavascriptBridge的暂时理解
直接从项目里复制了一份关于WebViewJavascriptBridge使用的代码,注释部分是自己暂时的理解.孟哥说,callHandler类似于jq里的trigger, registerHandle ...
- 知方可补不足~Sqlserver中的几把锁和.net中的事务级别
回到目录 当数据表被事务锁定后,我们再进行select查询时,需要为with(锁选项)来查询信息,如果不加,select将会被阻塞,直到锁被释放,下面介绍几种SQL的锁选项 SQL的几把锁 NOLOC ...
- javascript里阻止事件冒泡
如下图所示,灰色块包含红色块,假设我们为灰色和红色块各绑定一个单击弹框事件,当我们点击红色块时,不希望触发灰色块的弹框事件,这就需要阻止冒泡事件了. IE里阻止冒泡事件使用cancelBubble属性 ...