Java的编译器没有保留泛型方法注释?

Kra*_*ssi 11 java generics reflection annotations

我目前遇到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) {
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,当我使用反射查询有关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));
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

(意外)错误消息如下所示:

java.lang.AssertionError:方法'public void test.MyClass.hello(java.lang.Object)'未注释.

用Java 1.7.60测试.

biz*_*lop 4

正如其他人所指出的,编译生成两个具有相同名称的方法, ahello(Object)和 a hello(MyObject)

原因是类型擦除:

MyGenericInterface mgi = new MyClass();
c.hello( "hahaha" );
Run Code Online (Sandbox Code Playgroud)

上面的代码应该可以编译,因为删除了void hello(T)is 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);
     }
}
Run Code Online (Sandbox Code Playgroud)

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这应该可以正常工作。

更新:根据此 RFE,现在也应该将注释复制到桥接方法。