在不同的 Java 版本中运行时 getCanonicalName() 返回 null

Haw*_*awk 5 java

public class MyClass{
    public void action() {
    }
    public void test(){
        MyClassNameTest.takeAction(this::action);
    }
}

public interface MyInterface {
    public void action();
    default void printClassName(){
        System.out.println("java version: " + System.getProperty("java.version"));
        System.out.println("getCanonicalName: " + getClass().getCanonicalName());
        System.out.println("getName: " + getClass().getName());
        System.out.println("isAnonymousClass: " + getClass().isAnonymousClass());
    }
}
Run Code Online (Sandbox Code Playgroud)

运行这个类:

public class MyClassNameTest {
    public static void main(String[] args) {
        new MyClass().test();
    }
    static void takeAction(MyInterface myInterface) {
        myInterface.printClassName();
        myInterface.action();
    }
}
Run Code Online (Sandbox Code Playgroud)

重现步骤

  1. 在 java 1.8 或 11 中编译上述类
  2. 在 java 15 或更高版本中运行 MyClassNameTest

当前结果

getClass().getCanonicalName()返回空值

如果您在编译时在相同的 Java 版本中运行它们,getCanonicalName()则返回一个非空字符串。

我发现了有趣的结果,但我不知道原因。有人知道吗?

Swe*_*per 4

经过一番调查后,这显然是因为从 Java 15 开始,方法引用和 lambda(或者更确切地说,invokedynamic)现在是使用隐藏类来实现的,至少在 OpenJDK 中是这样。简而言之,隐藏类是在运行时动态定义的类,使用Lookup.defineHiddenClass,没有二进制名称,并且不需要类加载器来加载。

也尝试打印getClass().isHidden()printClassName. 它应该打印true

您还可以比较Java 15 实现InnerClassLambdaMetafactoryJava 8 实现。后者使用已弃用的sun.misc.Unsafe.defineAnonymousClass.

根据引入隐藏类(也在 Java 15 中实现)的 JEP,getCanonicalName返回 null 是预期的行为:

通过该Class对象,可以实例化隐藏类并像普通类一样访问其成员,但有四个限制:

  1. Class::getName返回一个不是二进制名称的字符串,如前所述。

  2. Class::getCanonicalName返回 null,表示隐藏类没有规范名称。(请注意,Class Java 语言中匿名类的对象具有相同的行为。)

  3. [...]

归根结底,方法引用表达式生成的对象仅具有JLS 本节中规定的保证。实现可能随时发生变化,本例就是一个例子。