我目前遇到了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"测验MyClass.hello(java.lang.Object)'未进行注释。
使用Java 1.7.60进行测试。
正如其他人所指出的,编译生成两个同名的方法,一个hello(Object)
和一个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)
。正如你在John Farrelly的答案中的字节码中看到的那样。
所以你的类看起来确实是这样的(观察你的注释是如何保持在原始方法上的):
public class MyClass implements MyGenericInterface<MyObject> {
@MyAnnotation
public void hello(final MyObject there) {
}
@Override
public void hello(Object ob) {
hello((MyObject)ob);
}
}
幸运的是,由于这是一种合成方法,您可以通过检查method.isSynthetic()
的值来筛选出void hello(Object)
,如果它是真的,则出于注释处理的目的,您应该忽略它。
@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,注释现在也应该复制到桥接方法。
似乎在内部,javac
创建了两个方法:
$ 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 中修复的一个错误
该方法出现两次。一个有注释,另一个没有。我想你也是这样,但断言错误发生在坏的断言上,你看不到好的断言。
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.
EDIT:正如我和其他人所证实的,该方法会被编译器复制。它是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);
}
}
它与这个问题有关:将派生类传递给一个需要重写期望基类的方法
我认为这也有关系。字段重复,因为方法为:使用JAXB 生成的XML中的重复字段