Java编译器不保留泛型方法注释?
我目前在Java的泛型类型擦除和运行时注释方面遇到了一个问题,我不确定我是否做错了什么,或者这是Java编译器中的一个bug。考虑下面的最小工作示例:
@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 with reflection我希望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: Method 'public void test.MyClass.hello(java.lang.Object)' is not annotated.
使用Java1.7.60进行测试
# 1 楼答案
该方法出现了两次。一个带注释,另一个不带注释。我猜这也是你的情况,但断言错误发生在坏的一个上,你看不到好的
输出为:
编辑:正如我和其他人确认的那样,编译器复制了这个方法。java需要它。我找到了正确的方法:
它与这个问题有关:Passing Derived Class to a method which needs to override expecting a base class
我觉得这也有关系。由于方法为: Duplicated field in generated XML using JAXB
# 2 楼答案
正如其他人所指出的,编译生成两个同名的方法,一个
hello(Object)
和一个hello(MyObject)
原因是类型擦除:
上面的代码应该编译,因为
void hello(T)
的擦除是void hello(Object)
。当然,它在运行时也应该失败,因为没有任何实现会接受任意的Object
从上面我们可以得出结论
void hello(MyObject)
实际上不是该方法的有效重写。但是,如果不能用类型参数“重写”一个方法,泛型就真的没用了编译器绕过它的方法是生成一个带有签名
void hello(Object)
的合成方法,该方法在运行时检查输入参数类型,如果检查成功,则委托给void hello(MyObject)
。如John Farrelly's answer中的字节码所示因此,您的类看起来确实是这样的(观察注释如何保持在原始方法上):
幸运的是,因为它是一种合成方法,所以可以通过检查
void hello(Object)
的值来过滤method.isSynthetic()
,如果它是真的,那么为了注释处理的目的,应该忽略它这应该行得通
更新:根据this RFE,注释现在也应该复制到桥接方法
# 3 楼答案
似乎在内部,
javac
创建了两种方法:hello(java.lang.Object)
方法检查对象的类型,然后调用带有注释的MyObject
方法更新
我看到这些额外的“桥接方法”被专门称为类型擦除和泛型的一部分:
https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html
此外,这些桥接方法is a bug which is fixed in Java 8 u94上缺少的注释