我在应用程序代码中有一个奇怪的错误,它是一个注释处理器,当我使用反射式查询该类时,我发现该错误的根本原因是类com.sun.tools.javac.tree.JCTree$JCClassDecl
两次包含方法getSimpleName()
方法getMethods()
。这两个版本的区别仅在于返回类型。这在JVM代码中合法,但在Java中不合法。这不是方法重载,因为只有返回类型有所不同,并且返回类型不是方法签名的一部分。
可以用简单的代码演示该问题:
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机器上。
问题:此类代码是如何创建的?我的理解是,这部分代码是用Java编写的,但是用Java是不可能的。另外:拥有两个相同签名版本的方法的目的是什么?有提示吗?
如果您查看source code 1,您将看到只有一种方法的名称为getSimpleName()
。此方法返回com.sun.tools.javac.util.Name
。关于这一点,有两点需要注意:
com.sun.source.tree.ClassTree#getSimpleName()
的javax.lang.model.element.Name
。com.sun.tools.javac.util.Name
抽象类实现了javax.lang.model.element.Name
接口,并且由于重写的方法返回了前者,因此它利用了covariant return types。根据this Oracle blog,使用桥方法实现一种覆盖另一个方法但声明协变返回类型的方法。
这是如何实现的?
尽管Java语言不允许基于返回类型的重载,但JVM始终允许基于返回类型的重载。 JVM使用方法的完整签名进行查找/解析。完整签名除了参数类型外还包括返回类型。即,一个类可以具有两个或多个方法,只是返回类型不同。 javac使用此事实来实现协变返回类型。在上面的CircleFactory示例中,javac生成的代码等效于以下代码:
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; } }
我们可以在类上使用带有-c选项的javap进行验证。注意,我们仍然不能在源语言中使用基于返回类型的重载。但是,javac使用它来支持协变返回类型。这样,JVM就不需要更改即可支持协变返回类型。
实际上,如果您运行以下命令:
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;
如您所见,第二种方法,即返回javax.lang.model.element.Name
的方法,既是synthetic又是bridge。换句话说,该方法由编译器生成,作为协变返回类型的实现的一部分。它还简单地委托给“真实”方法,该方法实际存在于源代码中,该方法返回com.sun.tools.javac.util.Name
。
1。源代码链接适用于JDK 13。