почему getSimpleName () дважды в com.sun.tools.javac.tree.JCTree $ JCClassDecl

У меня была странная ошибка в коде приложения, которое является обработчиком аннотаций, и я мог обнаружить, что основная причина ошибки заключалась в том, что класс 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 это невозможно. Также: какова цель иметь две версии метода с одинаковой сигнатурой? Намек?


person Peter Verhas    schedule 13.12.2019    source источник


Ответы (1)


Если вы посмотрите на исходный код 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, и поскольку переопределенный метод возвращает первый, он использует ковариантные возвращаемые типы.

Согласно этом блоге Oracle, метод, который отменяет другой, но объявляет, что ковариантный возвращаемый тип реализуется с помощью методов моста.

Как это реализовано?

Хотя перегрузка на основе возвращаемого типа не разрешена языком 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;
    }  
}

Мы можем использовать javap с опцией -c в классе, чтобы проверить это. Обратите внимание, что мы по-прежнему не можем использовать перегрузку на основе возвращаемого типа в исходном языке. Но это используется 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, - это синтетический и bridge. Другими словами, метод генерируется компилятором как часть реализации ковариантных возвращаемых типов. Он также просто делегирует «реальный» метод, фактически присутствующий в исходном коде, который возвращает com.sun.tools.javac.util.Name.


1. Ссылка на исходный код предназначена для JDK 13.

person Slaw    schedule 13.12.2019
comment
В этой статье stackoverflow.com/questions/18655541/ также является важной информацией, чтобы понять, почему существует потребность в синтетическом методе. - person Peter Verhas; 30.01.2020