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

问题描述 投票:0回答:1

我在应用程序代码中有一个奇怪的错误,它是一个注释处理器,当我使用反射式查询该类时,我发现该错误的根本原因是类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是不可能的。另外:拥有两个相同签名版本的方法的目的是什么?有提示吗?

java reflection annotation-processing annotation-processor
1个回答
1
投票

如果您查看source code 1,您将看到只有一种方法的名称为getSimpleName()。此方法返回com.sun.tools.javac.util.Name。关于这一点,有两点需要注意:

  1. 该方法实际上是重写声明为返回com.sun.source.tree.ClassTree#getSimpleName()javax.lang.model.element.Name
  2. 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。

© www.soinside.com 2019 - 2024. All rights reserved.