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 #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,即点击按钮之前的快照,看到的是:

本文原始链接 http://unmi.cc/jdk8-lambda-3-inside/,
来自 隔叶黄莺 Unmi Blog

只加载了两个类,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 表达式时才动态的去创建相应的实现类,并加载执行。

时间: 2024-10-06 21:23:29

JDK8 的 Lambda 表达式原理的相关文章

C++拾遗--lambda表达式原理

C++拾遗--lambda表达式原理 前言 lambda表达式是在C++11新标准中提出的.在lambda表达式中,我们集中梳理了一下它的使用.现在来讨论下它的实现原理. 正文 1.函数对象 类的对象跟括号结合,表现出函数一般的行为,这个对象可以称作是函数对象. #include <iostream> using namespace std; class MyClass { public: //重载函数调用运算符() int operator()(int i) { return i; } };

通过字节码分析JDK8中Lambda表达式编译及执行机制

关于Lambda字节码相关的文章,很早之前就想写了,[蜂潮运动]APP 产品的后端技术,能快速迭代,除了得益于整体微服架构之外,语言层面上,也是通过Java8的lambda表达式的运用以及rxJava响应式编程框架,使代码更加简洁易维护,调用方式更加便捷.本文将介绍JVM中的方法调用相关的字节码指令,重点解析JDK7(JSR-292)之后新增的invokedynamic指令给lambda表达式的动态调用特性提供的实现机制,最后再探讨一下lambda性能方面的话题. 方法调用的字节码指令 在介绍i

JDK8的lambda表达式、方法引用

(部分转自:https://www.cnblogs.com/xiaoxi/p/7099667.html) 1. lambda表达式 以前定义一个Thread: 1 final int i = 0; 2 new Thread(new Runnable() { 3 @Override 4 public void run() { 5 System.out.println("i = " + i); 6 } 7 }).start(); lambda表达式定义: 1 int i = 0; 2 ne

Lambda表达式-Java

package com.sxt.thread; /** * Lambda表达式 简化线程(用一次)的使用 * */ public class LambdaThread { //静态内部类 static class Test implements Runnable{ public void run() { for(int i=0;i<20;i++) { System.out.println("一边听歌"); } } } public static void main(String[

JDK8新特性 Lambda表达式

一.接口的默认方法二.Lambda 表达式三.函数式接口四.方法与构造函数引用五.Lambda 作用域六.访问局部变量七.访问对象字段与静态变量八.访问接口的默认方法九.Date API十.Annotation 注解:支持多重注解 一.接口的默认方法 Java8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下: [java] view plain copy public interface Formula { double calcu

JDK8的随笔(07)_行云流水般的Lambda表达式

好久没有更新啦,继续继续. 最近这个项目陷入了一个使用语言的怪圈.任何东西都想着原来的写法怎么能翻译到新的JDK 的写法.这其实就走入了歧途哇哇哇. 看下面这个例子,是一个很简单的例子.一般情况下我们都会这么写这样的逻辑. public static void main(String ... args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b");

JDK8的新特性——Lambda表达式

JDK8已经发布快4年的时间了,现在来谈它的新特性显得略微的有点“不合时宜”.尽管JDK8已不再“新”,但它的重要特性之一——Lambda表达式依然是不被大部分开发者所熟练运用,甚至不被开发者所熟知. 国内的开发环境大家都知道,有各种的老项目,有各种各样的发布风险,让公司以及项目组对新的技术往往望而却步,有公司甚至时至今日还在使用JDK6来进行项目开发,这导致了在很多技术的选择上受到了很大限制,进而不能跟随时代的脚步使得项目甚至公司一步一步走向衰落. 本文简单认识JDK8的重要新特性之一——La

Java 8 动态类型语言Lambda表达式实现原理分析

Java 8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标. import java.util.function.Consumer; public class Lambda { public static void main(String[] args) { Consumer<String> c = s -> System.out.println(s); c.accept("hello lambd

JDK8新特性02 Lambda表达式02_Lambda语法规则

//函数式接口:只有一个抽象方法的接口称为函数式接口. 可以使用注解 @FunctionalInterface 修饰 @FunctionalInterface public interface MyFun { public Integer getValue(Integer num); } import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List;