为什么 getSimpleName() 在 com.sun.tools.javac.tree.JCTree$JCClassDecl 中出现两次

why getSimpleName() is twice in com.sun.tools.javac.tree.JCTree$JCClassDecl

我在应用程序代码中有一个奇怪的错误,它是一个注释处理器,我发现错误的根本原因是 class com.sun.tools.javac.tree.JCTree$JCClassDecl 包含方法 getSimpleName() 当我使用反射方法 getMethods() 查询 class 时两次。这两个版本仅在 return 类型上有所不同。这在 JVM 代码中是合法的,但在 Java 中是不合法的。这不是方法重载,因为只有 return 类型不同,而 return 类型不是方法签名的一部分。

问题可以用简单的代码来演示:

Method[] methods = com.sun.tools.javac.tree.JCTree.JCClassDecl.class.getMethods();
for (int i = 0; i < methods.length; i++) {
    System.out.println(methods[i]);
}

它将打印

...
public javax.lang.model.element.Name com.sun.tools.javac.tree.JCTree$JCClassDecl.getSimpleName()
public com.sun.tools.javac.util.Name com.sun.tools.javac.tree.JCTree$JCClassDecl.getSimpleName()
...

(省略号代表更多输出行,显示我们现在不感兴趣的各种其他方法。)

我用来测试的Java版本是

$ java -version
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)

在 Windows 10 台机器上。

问题:这个 class 代码是如何创建的?我的理解是这部分代码是写在Java里的,但是在Java里这是不可能的。另外:拥有一个方法的两个相同签名版本的目的是什么?有什么提示吗?

如果您查看 source code1,您会发现只有一个方法的名称为 getSimpleName()。这个方法returns com.sun.tools.javac.util.Name。有两点需要注意:

  1. 该方法实际上覆盖了声明给 return javax.lang.model.element.Name.
  2. com.sun.source.tree.ClassTree#getSimpleName()
  3. com.sun.tools.javac.util.Name 抽象 class 实现了 javax.lang.model.element.Name 接口,并且由于覆盖方法 return 是前者,它利用了 covariant return types

根据 this Oracle blog,一个覆盖另一个但声明协变 return 类型的方法是使用桥接方法实现的。

How is this implemented?

Although the return type based overloading is not allowed by java language, JVM always allowed return type based overloading. JVM uses full signature of a method for lookup/resolution. Full signature includes return type in addition to argument types. i.e., a class can have two or more methods differing only by return type. javac uses this fact to implement covariant return types. In the above, CircleFactory example, javac generates code which is equivalent to the following:

class CircleFactory extends ShapeFactory {
    public Circle newShape() {
       // your code from the source file
       return new Circle();
    }
    // javac generated method in the .class file
    public Shape newShape() {
       // call the other newShape method here -- invokevirtual newShape:()LCircle;
    }  
}

We can use javap with -c option on the class to verify this. Note that we still can't use return type based overloading in source language. But, this is used by javac to support covariant return types. This way, there is no change needed in the JVM to support covariant return types.

事实上,如果您运行执行以下命令:

javap -v com.sun.tools.javac.tree.JCTree$JCClassDecl

输出如下(只包括相关方法):

public com.sun.tools.javac.util.Name getSimpleName();
descriptor: ()Lcom/sun/tools/javac/util/Name;
flags: (0x0001) ACC_PUBLIC
Code:
  stack=1, locals=1, args_size=1
     0: aload_0
     1: getfield      #13                 // Field name:Lcom/sun/tools/javac/util/Name;
     4: areturn
  LineNumberTable:
    line 801: 0
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0       5     0  this   Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;

并且:

public javax.lang.model.element.Name getSimpleName();
descriptor: ()Ljavax/lang/model/element/Name;
flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
  stack=1, locals=1, args_size=1
     0: aload_0
     1: invokevirtual #96                 // Method getSimpleName:()Lcom/sun/tools/javac/util/Name;
     4: areturn
  LineNumberTable:
    line 752: 0
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0       5     0  this   Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;

如您所见,第二种方法,即 return 和 javax.lang.model.element.Name 的方法都是 synthetic and a bridge。换句话说,该方法由编译器生成,作为协变 return 类型实现的一部分。它还简单地委托给 "real" 方法,该方法实际存在于 returns com.sun.tools.javac.util.Name.

的源代码中

1.源代码 link 用于 JDK 13.