JDK8 使用一行 Lambda 表达式能够代替先前用匿名类五六行代码所作的事情,那么它是怎么实现的呢?从所周知,匿名类会在编译的时候生成与宿主类带上 $1, $2 的类文件,如写在 TestLambda 中的匿名类产生成类文件是 TestLambda$1.class, TestLambda$2.class 等。java
我试验了一下,若是使用的是 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"
);
}
});
}
}
|
相应的字节码以下:ide
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。spa
为此,我专门作了个实验,下面的代码编译会生成两个类文件,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 等。code
命令:orm
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。
上面使用的 JDK 工具是 jmap 和 jhat,也能够用 Java VisualVM -- 即 jvisualvm 指令来查看执行 lambda 先后的内存快照。截了两个图:
上图为点击按钮前的快照,能够看到只有两个类
上图是点击按钮后的快照,又多了 cc.unmi.testjdk8.TestLambda$1$$Lambda$1 这个类。
小结:JDK8 在实现 Lambda 时使用了 JDK7 虚拟机开始有的 invokedynamic 方法调用指令,该知使得虚拟机执行到 Lambda 表达式时才动态的去建立相应的实现类,并加载执行。