Компилятор Java не сохраняет общие аннотации методов?
В настоящее время я сталкиваюсь с проблемой с аннотациями стирания Java-типа общего вида и времени выполнения, и я не уверен, что я делаю что-то неправильно или это ошибка в компиляторе Java. Рассмотрим следующий минимальный рабочий пример:
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
}
public interface MyGenericInterface<T> {
void hello(T there);
}
public class MyObject {
}
public class MyClass implements MyGenericInterface<MyObject> {
@Override
@MyAnnotation
public void hello(final MyObject there) {
}
}
Теперь, когда я запрашиваю информацию о MyClass.hello с отражением, я ожидаю, что метод hello все еще имеет аннотацию, однако это не так:
public class MyTest {
@Test
public void testName() throws Exception {
Method[] declaredMethods = MyClass.class.getDeclaredMethods();
for (Method method : declaredMethods) {
Assert.assertNotNull(String.format("Method '%s' is not annotated.", method), method
.getAnnotation(MyAnnotation.class));
}
}
}
Сообщение об ошибке (неожиданное) выглядит следующим образом:
java.lang.AssertionError: метод 'public void test.MyClass.hello(java.lang.Object) 'не аннотируется.
Протестировано с помощью Java 1.7.60.
Ответы
Ответ 1
Как было указано другими, компиляция генерирует два метода с тем же именем: hello(Object)
и a hello(MyObject)
.
Причиной этого является стирание типа:
MyGenericInterface mgi = new MyClass();
c.hello( "hahaha" );
Вышеприведенное должно компилироваться, поскольку стирание void hello(T)
равно void hello(Object)
. Конечно, он также должен быть неудачным во время выполнения, потому что нет реализации, которая бы принимала произвольное Object
.
Из вышесказанного можно сделать вывод, что void hello(MyObject)
на самом деле не является допустимым переопределением для этого метода. Но дженерики были бы бесполезны, если вы не смогли бы "переопределить" метод с параметром типа.
Способ, которым компилятор обходит его, заключается в создании синтетического метода с сигнатурой void hello(Object)
, которая проверяет тип входного параметра во время выполнения и делегирует на void hello(MyObject)
, если проверка выполнена успешно. Как вы можете видеть в байтовом коде в ответ Джона Фаррелли.
Итак, ваш класс действительно выглядит примерно так (обратите внимание, как ваша аннотация остается на исходном методе):
public class MyClass implements MyGenericInterface<MyObject> {
@MyAnnotation
public void hello(final MyObject there) {
}
@Override
public void hello(Object ob) {
hello((MyObject)ob);
}
}
К счастью, поскольку это синтетический метод, вы можете отфильтровать void hello(Object)
, проверив значение method.isSynthetic()
, если оно истинно, вы должны просто игнорировать его для целей обработки аннотаций.
@Test
public void testName() throws Exception {
Method[] declaredMethods = MyClass.class.getDeclaredMethods();
for (Method method : declaredMethods) {
if (!method.isSynthetic()) {
Assert.assertNotNull(String.format("Method '%s' is not annotated.", method), method
.getAnnotation(MyAnnotation.class));
}
}
}
Это должно работать нормально.
Обновление: Согласно эта RFE, теперь аннотации должны быть скопированы и для мостовых методов.
Ответ 2
Кажется, что внутри javac
создано 2 метода:
$ javap -c MyClass.class
Compiled from "MyTest.java"
class MyClass implements MyGenericInterface<MyObject> {
MyClass();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object." <init>":()V
4: return
public void hello(MyObject);
Code:
0: return
public void hello(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #2 // class MyObject
5: invokevirtual #3 // Method hello:(LMyObject;)V
8: return
}
Метод hello(java.lang.Object)
проверяет тип объекта, а затем вызывает метод MyObject
, который имеет аннотацию на нем.
Обновление
Я вижу, что эти дополнительные "мостовые методы" специально вызываются как часть стирания и дженериков типов:
https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html
Кроме того, аннотации, отсутствующие в этих мостах являются ошибкой, которая исправлена в Java 8 u94
Ответ 3
Метод появляется дважды. Один из них аннотирован, а другой нет. Я думаю, это тоже ваше дело, но ошибка утверждения происходит на плохой, и вы не можете увидеть хорошее.
Method[] declaredMethods = MyClass.class.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(String.format("Method: %s", method));
for (Annotation a: method.getAnnotations()) {
System.out.println(String.format(" Annotation: %s of class %s", a, a.annotationType()));
}
for (Annotation a: method.getDeclaredAnnotations()) {
System.out.println(String.format(" DeclaredAnnotation: %s of class %s", a, a.annotationType()));
}
if (method.getDeclaredAnnotation(MyAnnotation.class) == null) {
System.out.println(String.format(
" Method '%s' is not annotated.", method));
}
}
Выход:
Method: public void MyClass.hello(MyObject)
Annotation: @MyAnnotation() of class interface MyAnnotation
DeclaredAnnotation: @MyAnnotation() of class interface MyAnnotation
Method: public void MyClass.hello(java.lang.Object)
Method 'public void MyClass.hello(java.lang.Object)' is not annotated.
РЕДАКТИРОВАТЬ: Как я подтвердил, и другие подтвердили, что его метод дублируется компилятором. Это необходимо java. Я добрался до декомпилировать его правильный путь:
//# java -jar ........\cfr_0_101.jar MyClass --hidebridgemethods false
/*
* Decompiled with CFR 0_101.
*/
import MyAnnotation;
import MyGenericInterface;
import MyObject;
public class MyClass
implements MyGenericInterface<MyObject> {
@MyAnnotation
@Override
public void hello(MyObject there) {
}
@Override
public /* bridge */ /* synthetic */ void hello(Object object) {
MyClass myClass;
myClass.hello((MyObject)object);
}
}
Это связано с этим вопросом: Передача производного класса методу, который должен переопределить ожидающий базовый класс
Я думаю, что это тоже связано. Поле дублируется, потому что метод:
Дублированное поле в сгенерированном XML с использованием JAXB