有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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进行测试


共 (3) 个答案

  1. # 1 楼答案

    该方法出现了两次。一个带注释,另一个不带注释。我猜这也是你的情况,但断言错误发生在坏的一个上,你看不到好的

    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);
        }
    }
    

    它与这个问题有关:Passing Derived Class to a method which needs to override expecting a base class

    我觉得这也有关系。由于方法为: Duplicated field in generated XML using JAXB

  2. # 2 楼答案

    正如其他人所指出的,编译生成两个同名的方法,一个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's answer中的字节码所示

    因此,您的类看起来确实是这样的(观察注释如何保持在原始方法上):

    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));
            }
        }
    }
    

    这应该行得通

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

  3. # 3 楼答案

    似乎在内部,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

    此外,这些桥接方法is a bug which is fixed in Java 8 u94上缺少的注释