背景
大家在日常开发中应该都用过 表达式。以下面的 代码为例,我们在运行 中的 方法时,可以看到 的类型信息,但是在编译时、运行时都没有看到 对应的类的 文件,有没有办法将 对应的类保存到 文件里呢?本文会对此进行探讨。
public class HelloWorld {
public static void main(String[] args) {
Runnable r = () -> System.out.println("Hello World");
r.run();
System.out.println("class name of r: " + r.getClass().getName());
}
}
要点
运行 命令时,使用以下两个选项中的任何一个,都可以把 表达式对应的类自动保存到 文件中
-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true
正文
查看 版本和 版本
查看 版本
使用如下命令可以查看 命令的版本
javac -version
该命令在我电脑上的运行结果如下
javac 21
查看 版本
使用如下命令可以查看 命令的版本
java -version
该命令在我电脑上的运行结果如下
openjdk version "21" 2023-09-19
OpenJDK Runtime Environment (build 21+35-2513)
OpenJDK 64-Bit Server VM (build 21+35-2513, mixed mode, sharing)
代码
请将如下代码保存为 ⬇️
public class HelloWorld {
public static void main(String[] args) {
Runnable r = () -> System.out.println("Hello World");
r.run();
System.out.println("class name of r: " + r.getClass().getName());
}
}
编译
使用如下的命令可以编译
javac -parameters HelloWorld.java
编译之后,执行 tree . 命令,会看到这样的结果 ⬇️ (当前目录多了 文件)
.
├── HelloWorld.class
└── HelloWorld.java
1 directory, 2 files
运行
使用如下的命令可以运行 中的 方法 ⬇️
java HelloWorld
在我电脑上,该命令的运行结果如下 ⬇️ (在您的电脑上,最后一行输出的类名可能是其他值)
Hello World
class name of r: HelloWorld$$Lambda/0x000000d001000a00
看来 的精确类型(为了便于描述,我们把它简称为 吧)是一个很特殊的类(至少命名很特殊😂)。在运行完 中的 方法之后,当前目录并没有新的文件生成。如果我们想看看 的内容,一个思路是让 虚拟机把 保存到 文件里(我们可以用 命令来查看 文件的内容)。
在 InnerClassLambdaMetafactory.java 里可以找到如下的选项 ⬇️
看起来只要加上以下两个选项的任何一个,就可以将 对应的类保存到 文件里
-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true
我们就用第一个选项吧,完整的命令如下 ⬇️
java -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles HelloWorld
运行完这个命令后,再运行 tree . 命令,会得到以下结果 ⬇️ (在您的电脑上,所看到的 文件的名称可能会有差异)
.
├── DUMP_LAMBDA_PROXY_CLASS_FILES
│ └── HelloWorld$$Lambda.0x000000f801000a00.class
├── HelloWorld.class
└── HelloWorld.java
2 directories, 3 files
当前目录多了一个名为 DUMP_LAMBDA_PROXY_CLASS_FILES 的目录,这个目录下有一个 文件。
反编译
我们用如下的命令可以查看这个 文件的内容
javap -v -p DUMP_LAMBDA_PROXY_CLASS_FILES/*class
我电脑上的运行结果如下 ⬇️ (这里略去了结果中的前三行)
final class HelloWorld$$Lambda implements java.lang.Runnable
minor version: 0
major version: 65
flags: (0x1030) ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
this_class: #2 // HelloWorld$$Lambda
super_class: #4 // java/lang/Object
interfaces: 1, fields: 0, methods: 2, attributes: 0
Constant pool:
#1 = Utf8 HelloWorld$$Lambda
#2 = Class #1 // HelloWorld$$Lambda
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 java/lang/Runnable
#6 = Class #5 // java/lang/Runnable
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = NameAndType #7:#8 // "<init>":()V
#10 = Methodref #4.#9 // java/lang/Object."<init>":()V
#11 = Utf8 run
#12 = Utf8 HelloWorld
#13 = Class #12 // HelloWorld
#14 = Utf8 lambda$main$0
#15 = NameAndType #14:#8 // lambda$main$0:()V
#16 = Methodref #13.#15 // HelloWorld.lambda$main$0:()V
#17 = Utf8 Code
{
private HelloWorld$$Lambda();
descriptor: ()V
flags: (0x0002) ACC_PRIVATE
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
public void run();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #16 // Method HelloWorld.lambda$main$0:()V
3: return
}
由于它的内容并不多,可以尝试手动反编译它,反编译的结果如下 ⬇️
// 以下内容是我手动反编译的结果,不保证完全准确,仅供参考
final class HelloWorld$$Lambda implements java.lang.Runnable {
private HelloWorld$$Lambda() {
super();
}
public void run() {
HelloWorld.lambda$main$0();
}
}
注意到 run() 方法里会调用 HelloWorld 中的静态方法 lambda$main$0。但是我们在 中并没有定义这样的静态方法,那我们顺便把 的内容也看一下吧 ⬇️ (使用如下的命令就可以查看 文件的详细内容)
javap -v -p HelloWorld
完整的结果有点长,这里就不展示了。基于 命令的运行结果,我手动进行了反编译,反编译的结果如下 ⬇️
// 以下内容是我手动反编译的结果,不保证完全准确,仅供参考
public class HelloWorld {
public HelloWorld() {
super();
}
public static void main(String[] arg) {
// 这里涉及一些特殊字节码指令,有点复杂,所以略去具体的内容
}
// 这是一个合成方法,原始的 java 代码中没有它
private static void lambda$main$0() {
System.out.println("Hello World");
}
}
简要的类图如下 ⬇️
另一个例子
我们再看看另一个例子 ⬇️ (请将以下 代码保存为 )
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class SimpleCode {
public static void main(String[] args) {
Runnable runnable = () -> System.out.println("Hello World");
Supplier<String> supplier = () -> "Hello World";
IntPredicate predicate = (num) -> num % 2 == 0; // Check whether num is an even number
IntConsumer consumer = System.out::println;
System.out.println(runnable.getClass().getName());
System.out.println(supplier.getClass().getName());
System.out.println(predicate.getClass().getName());
System.out.println(consumer.getClass().getName());
}
}
编译和运行
使用如下的命令可以编译
javac -parameters SimpleCode.java
可以使用如下的命令来运行 中 方法
java -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles SimpleCode
在我的电脑上看到的运行结果如下 ⬇️ (在您的电脑上,看到的结果可能会有差异)
SimpleCode$$Lambda/0x000000b801000a00
SimpleCode$$Lambda/0x000000b801000c18
SimpleCode$$Lambda/0x000000b801001000
SimpleCode$$Lambda/0x000000b801001248
此时再执行 tree . 命令,应该能看到类似这样的效果 ⬇️ (在您的电脑上,运行时生成的 文件的名称可能会有差异)
.
├── DUMP_LAMBDA_PROXY_CLASS_FILES
│ ├── SimpleCode$$Lambda.0x000000b801000a00.class
│ ├── SimpleCode$$Lambda.0x000000b801000c18.class
│ ├── SimpleCode$$Lambda.0x000000b801001000.class
│ └── SimpleCode$$Lambda.0x000000b801001248.class
├── SimpleCode.class
└── SimpleCode.java
2 directories, 6 files
反编译
可以借助 命令来查看各个 文件的内容(具体的过程就不赘述了)
DUMP_LAMBDA_PROXY_CLASS_FILES 目录下的 个 文件反编译的结果如下
// 以下内容是我手动反编译的结果,不保证完全准确,仅供参考
final class SimpleCode$$Lambda implements java.lang.Runnable {
private SimpleCode$$Lambda() {
super();
}
public void run() {
SimpleCode.lambda$main$0();
}
}
// 以下内容是我手动反编译的结果,不保证完全准确,仅供参考
final class SimpleCode$$Lambda implements java.util.function.Supplier {
private SimpleCode$$Lambda() {
super();
}
public java.lang.Object get() {
return SimpleCode.lambda$main$1();
}
}
// 以下内容是我手动反编译的结果,不保证完全准确,仅供参考
final class SimpleCode$$Lambda implements java.util.function.IntPredicate {
private SimpleCode$$Lambda() {
super();
}
public boolean test(int arg0) {
return SimpleCode.lambda$main$2(arg0);
}
}
// 以下内容是我手动反编译的结果,不保证完全准确,仅供参考
final class SimpleCode$$Lambda implements java.util.function.IntConsumer {
private final java.io.PrintStream arg$1;
private SimpleCode$$Lambda(java.io.PrintStream arg0) {
super();
this.arg$1 = arg0;
}
public void accept(int arg0) {
this.arg$1.println(arg0);
}
}
文件反编译的结果如下
public class SimpleCode {
public SimpleCode() {
super();
}
public static void main(java.lang.String[] args) {
// 这里涉及一些特殊字节码指令,有点复杂,所以略去具体的内容
}
// 这是一个合成方法,原始的 java 代码中没有它
private static boolean lambda$main$2(int arg0) {
return arg0 % 2 == 0;
}
// 这是一个合成方法,原始的 java 代码中没有它
private static java.lang.String lambda$main$1() {
return "Hello World";
}
// 这是一个合成方法,原始的 java 代码中没有它
private static void lambda$main$0() {
System.out.println("Hello World");
}
}
简要类图
其他
绘制“HelloWorld 的简要类图”所用到的代码
我用了 IntelliJ IDEA 中的 PlantUML 插件来画那张图,完整的代码如下 ⬇️
@startuml
'https://plantuml.com/component-diagram
title <i>HelloWorld</i> 的简要类图
caption \n\n
' caption 的内容是为了防止掘金平台生成的水印遮盖图中的文字
class HelloWorld {
+ HelloWorld()
+ {static} void main(String[] args)
- {static} void lambda$main$0()
}
interface java.lang.Runnable
class HelloWorld$$Lambda
java.lang.Runnable <|.. HelloWorld$$Lambda
interface java.lang.Runnable {
void run()
}
class HelloWorld$$Lambda {
- HelloWorld$$Lambda()
+ void run()
}
note left of HelloWorld$$Lambda::run
<code>
public void run() {
HelloWorld.lambda$main$0();
}
</code>
end note
note left of HelloWorld::lambda$main$0
<i>lambda$main$0()</i> 是一个合成方法,
可以认为它的代码是这样的 <:point_down:>
<code>
private static void lambda$main$0() {
System.out.println("Hello World");
}
</code>
end note
@enduml
绘制“SimpleCode 的简要类图”所用到的代码
我用了 IntelliJ IDEA 中的 PlantUML 插件来画那张图,完整的代码如下 ⬇️
@startuml
'https://plantuml.com/component-diagram
title <i>SimpleCode</i> 的简要类图
caption \n\n
' caption 的内容是为了防止掘金平台生成的水印遮盖图中的文字
class SimpleCode {
+ SimpleCode()
+ {static} void main(java.lang.String[] args)
- {static} boolean lambda$main$2(int arg0)
- {static} String lambda$main$1()
- {static} void lambda$main$0()
}
class "SimpleCode$$Lambda" as L1
java.lang.Runnable <|.. L1
class L1 {
- SimpleCode$$Lambda()
+ void run()
}
interface java.lang.Runnable {
void run()
}
interface java.util.function.Supplier<T>
class "SimpleCode$$Lambda" as L2
java.util.function.Supplier <|.. L2
class L2 {
- SimpleCode$$Lambda()
+ Object get()
}
interface java.util.function.Supplier<T> {
T get()
}
interface java.util.function.IntPredicate
class "SimpleCode$$Lambda" as L3
java.util.function.IntPredicate <|.. L3
class L3 {
- SimpleCode$$Lambda()
+ boolean test(int arg0)
}
interface java.util.function.IntPredicate {
boolean test(int value)
}
interface java.util.function.IntConsumer
class "SimpleCode$$Lambda" as L4
java.util.function.IntConsumer <|.. L4
class L4 {
- final java.io.PrintStream arg$1
- SimpleCode$$Lambda(java.io.PrintStream arg0)
+ void accept(int arg0)
}
interface java.util.function.IntConsumer {
void accept(int value)
}
note left of L1::run
可以认为它的代码是这样的 <:point_down:>
<code>
public void run() {
SimpleCode.lambda$main$0();
}
</code>
end note
note left of L2::get
可以认为它的代码是这样的 <:point_down:>
<code>
public Object get() {
return SimpleCode.lambda$main$1();
}
</code>
end note
note left of L3::test
可以认为它的代码是这样的 <:point_down:>
<code>
public boolean test(int arg0) {
return SimpleCode.lambda$main$2(arg0);
}
</code>
end note
note left of L4::accept
可以认为它的代码是这样的 <:point_down:>
<code>
public void accept(int arg0) {
this.arg$1.println(arg0);
}
</code>
end note
@enduml