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 #250             // 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 表达式原理的更多相关文章

  1. Java8 Lambda表达式原理扫盲

    背景 在使用Lamdba表达式,一直以为是内部类的方式实现的,但是一想如果每次调用都实例化一个内部类,性能肯定不好,难道Java里的lambda表达式真的是这么实现的吗?也许是该研究下原理了. 正文 ...

  2. Java8新特性(一)——Lambda表达式与函数式接口

    一.Java8新特性概述 1.Lambda 表达式 2. 函数式接口 3. 方法引用与构造器引用 4. Stream API 5. 接口中的默认方法与静态方法 6. 新时间日期 API 7. 其他新特 ...

  3. java8学习之Lambda表达式初步与函数式接口

    对于Java8其实相比之前的的版本增加的内容是相当多的,其中有相当一大块的内容是关于Lambda表达式与Stream API,而这两部分是紧密结合而不能将其拆开来对待的,但是是可以单独使用的,所以从学 ...

  4. jdk8 Lambda表达式与匿名内部类比较

    Labmda表达式与匿名内部类 前言 Java Labmda表达式的一个重要用法是简化某些匿名内部类(Anonymous Classes)的写法.实际上Lambda表达式并不仅仅是匿名内部类的语法糖, ...

  5. jdk8新特性-亮瞎眼的lambda表达式

    jdk8之前,尤其是在写GUI程序的事件监听的时候,各种的匿名内部类,大把大把拖沓的代码,程序毫无美感可言!既然Java中一切皆为对象,那么,就类似于某些动态语言一样,函数也可以当成是对象啊!代码块也 ...

  6. Lambda表达式运行原理

    目录 一.创建测试样例 二.利用Java命令编译分析 三.文末 JDK8引入了Lambda表达式以后,对我们写代码提供了很大的便利,那么Lambda表达式是如何运用简单表示来达到运行效果的呢?今天,我 ...

  7. jdk8的新特性 Lambda表达式

    很多同学一开始接触Java8可能对Java8 Lambda表达式有点陌生. //这是一个普通的集合 List<Employee> list = em.selectEmployeeByLog ...

  8. JDK8的新特性——Lambda表达式

    JDK8已经发布快4年的时间了,现在来谈它的新特性显得略微的有点“不合时宜”.尽管JDK8已不再“新”,但它的重要特性之一——Lambda表达式依然是不被大部分开发者所熟练运用,甚至不被开发者所熟知. ...

  9. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

随机推荐

  1. iOS开发-NSOperation与GCD区别

    Mac OS X 10.6及iOS4.0之后导入了可以使全体线程更高效运行,并且使并行处理应用更易开发的架构,GCD(Grand Central  Dispatch),同时引入的还有Run Loop, ...

  2. 岁末年初3Q大战惊现高潮,360震撼推出Android "3Q" IM即时通讯

    岁末年初3Q大战惊现高潮,360震撼推出Android "3Q" IM即时通讯 看过了QQ和360斗争的开端高潮,当然现在还不能说这场斗争已经结束,在我看来这次的事件未尝不是一个适 ...

  3. 更高效地提高redis client多线程操作的并发吞吐设计

    Redis是一个非常高效的基于内存的NOSQL数据库,它提供非常高效的数据读写效能.在实际应用中往往是带宽和CLIENT库读写损耗过高导致无法更好地发挥出Redis更出色的能力.下面结合一些redis ...

  4. 【菜鸟玩Linux开发】在C++里操作MySQL

    MySQL是一个的开源关系型数据库,对于服务端开发来说是一个优秀的选择.本篇内容将介绍如何在C++程序里操作MySQL数据库. ———————————————————————————————————— ...

  5. 不写1行代码,在Mac上体验ASP.NET 5的最简单方法

    昨天微软发布了ASP.NET 5 beta2(详见ASP.NET 5 Beta2 发布),对ASP.NET 5的好奇心又被激发了. 今天下午在Mac OS X上体验了一下ASP.NET 5,而且借助Y ...

  6. 获取机器安装.NET版本的几种方式

    当调查应用程序问题时,通常需要先确认目标机器所安装的 .NET Framework 的版本.可以通过如下方式来确认版本号: 通过控制面板安装程序查询 通过查询注册表获取版本信息 通过查看安装目录获取版 ...

  7. 【C语言学习】《C Primer Plus》第10章 数组和指针

    学习总结 1.数组初始化方式: int a[]={1,2,3} int a[SIZE]={1,2,3} //SIZE是宏定义,数组初始化个数不能大于SIZE,否则报错:当个数小 //SIZE,自动补0 ...

  8. 浏览器兼容性小记-DOM篇(一)

    1.childNodes引入空白节点问题:使用childElementCount或children 2.innerText: FF中不支持该属性,使用textContent代替 3.变量名与某HTML ...

  9. 【异常处理_iis】无法启动IIS Express\iisexpress.exe

    正调试着程序,突然不能调试了.重启了也没用,还是报错:无法启动程序 C:\Program Files(X86)\IIS Express\iisexpress.exe. 和之前无法启动IIS Expre ...

  10. 手把手教你用python打造网易公开课视频下载软件4-图形化界面

    上一篇讲解完函数:def getdownLoadInfo (url): 传入公开课的url地址,就可以提取课程的信息,这一篇讲解一下如何编写图像化界面.大概思考一下图像化界面需要的内容: (1)一个标 ...