class 文件在文件系统上的含义

Meaning of class file on the file system

我正在学习 lambda 表达式,这本书说明了一个使用 toString() lambda 表达式方法的示例。

Supplier<ArrayList<String>> s1 = ArrayList<String>::new;
ArrayList<String> a1 = s1.get();
System.out.println(s1);
//Output: functionalinterface.BuiltIns$$Lambda/791452441@1fb3ebeb

它解释了输出的含义,

This actually does mean something. Our test class is named BuiltIns , and it is in a package that we created named functionalinterface . Then comes $$ , which means that the class doesn’t exist in a class file on the file system. It exists only in memory.

最后几句的意思我没看懂。你能表达出来吗?

来源:OCP: Oracle Certified Professional Java SE 8 Programmer II Study Guide, Jeanne Boyarsky, Scott Selikoff

When you create a class, you type out a .java file and compile it to a .class file. Both are actual files that exist somewhere on your filesystem. The $$ shows it is different. There is no .java class on the filesystem. Instead Java creates the class for us. That way you don't have to type out a class and can just supply the lambda expression.

作者回答,Ms.Boyarsky

嗯,这本书有点误导。该声明确实正确,但有一个小的更正。它 假定 如果您在 class 名称中包含 $,它将自动创建(不是由您创建);这可能不是事实。

例如假设这个例子:

public class TestSO {

   public static void main(String[] args) {
      Test t = new Test() {
      };
   }

   static class Test {}
}

您创建了一个匿名内部 class,它实际上是由编译器创建的普通 class。如果您编译 TestSO,您将看到一个名为 TestSO$1.class 的 class,它是为您创建的。如果你用 javap -c -p TestSO$1.class 检查它的外观,你会看到这样的东西:

final class TestSO extends TestSO$Test { ... 

但是同时声明一个包含$符号的class/method是完全合法的:

static class $$Test2$$ {}

因此 $$ 的存在并不强烈表明 class 是由 compiler/runtime 生成的。这也是一个实施细节,有一天可能会改变......

同时这本书是正确的 the class doesn’t exist in a class file on the file system. It exists only in memory.

Supplier 是一个接口,您还没有提供实现它的 class,是吗?那么接下来发生的事情很有趣。

我不打算详细介绍,但这里有一个简化的解释。

如果你反编译你的例子(javap -p -c -v TestSO.class)你会看到这样一行:

invokedynamic #2,  0  // InvokeDynamic #0:get:()Ljava/util/function/Supplier;

invokedynamic 所做的是让 runtime 决定如何提供 Supplier 的实际实例。 在运行时创建了一个实际的class来实现所使用的Supplier

你可以看到 class 看起来像 运行 时使用的命令:

-Djdk.internal.lambda.dumpProxyClasses=/Your/Path/Here

因此在该路径中您将看到一个 class 名为:

TestSO$$Lambda.class 

再一次,如果你用 javap -c -p TestSO$$Lambda$1.class 反编译它:

 final class TestSO$$Lambda 
         implements java.util.function.Supplier {....

作者想说的是,在运行时会为你生成一个class实现了Supplier接口,但不是你创建的。

这个引用是试图用更简单或更口语化的术语来解释一些事情,避免过于正式,但是,结果可以说是灾难性的。

这个名字实际上没有任何意义。为 lambda 表达式或方法引用提供的实例的 class is intentionally unspecified 及其名称也是如此。甚至未指定在执行 System.out.println(s1); 时隐式调用的 toString() 方法是否会产生“classname@hashcode”输出。

是否涉及文件系统无关紧要。正确和实际的一点是,在编译应用程序时,该实例的 class 不在 Java 编译器创建的 classes 中。相反,它由 JRE 在运行时以特定于实现的方式提供。 OpenJDK 确实提供了一个生成的 class,它恰好有一个包含包含 lambda 表达式的 class 和一个 $$ 的名称,但是,如前所述,这是一个实现细节。而且,,美元符号是 Java 标识符的合法部分,因此两个美元符号的存在并不能证明特定的 属性.