简体   繁体   English

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

[英]Java's compiler not retaining generic method annotations?

I am currently encountering an issue with Java's generic type erasure and runtime annotations and I am not sure whether I am doing something wrong or it is a bug in the Java compiler. 我目前遇到Java的泛型类型擦除和运行时注释的问题,我不确定我是做错了什么,或者它是Java编译器中的错误。 Consider the following minimal working example: 考虑以下最小的工作示例:

@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) {
    }
}

Now when I query information about MyClass.hello with reflection I would expect that the hello method still has the annotation, however it does not: 现在,当我使用反射查询有关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));
        }
    }

}

The (unexpected) error message reads as follows: (意外)错误消息如下所示:

java.lang.AssertionError: Method 'public void test.MyClass.hello(java.lang.Object)' is not annotated. java.lang.AssertionError:方法'public void test.MyClass.hello(java.lang.Object)'未注释。

Tested with Java 1.7.60. 用Java 1.7.60测试。

As has been pointed out by others, compilation generates two methods with the same name, a hello(Object) and a hello(MyObject) . 正如其他人所指出的,编译生成两个具有相同名称的方法,一个是hello(Object)和一个hello(MyObject)

The reason for this is type erasure: 原因是类型擦除:

MyGenericInterface mgi = new MyClass();
c.hello( "hahaha" );

The above should compile because the erasure of void hello(T) is void hello(Object) . 上面应该编译,因为void hello(T)的擦除是void hello(Object) Of course it should also fail at runtime because there is no implementation that would accept an arbitrary Object . 当然它也应该在运行时失败,因为没有可以接受任意Object

From the above we can conclude that void hello(MyObject) is not in fact a valid override for that method. 从上面我们可以得出结论, void hello(MyObject)实际上并不是该方法的有效覆盖。 But generics would be really useless if you couldn't "override" a method with a type parameter. 但是如果你不能用一个类型参数“覆盖”一个方法,那么泛型将毫无用处。

The way the compiler gets around it is to generate a synthetic method with the signature void hello(Object) , which checks the input parameter type at runtime and delegates to void hello(MyObject) , if the check is successful. 编译器绕过它的方法是生成一个带有签名void hello(Object)的合成方法,它在运行时检查输入参数类型,如果检查成功,则委托给void hello(MyObject) As you can see in the byte code in John Farrelly's answer . 正如你在John Farrelly的答案中的字节代码中看到的那样。

So your class really looks something like this (observe how your annotation stays on the original method): 所以你的类看起来像这样(观察你的注释如何保留在原始方法上):

public class MyClass implements MyGenericInterface<MyObject> {
     @MyAnnotation
     public void hello(final MyObject there) {
     }

     @Override
     public void hello(Object ob) {
         hello((MyObject)ob);
     }
}

Luckily, because it's a synthetic method, you can filter out void hello(Object) by checking the value of method.isSynthetic() , if it's true you should just ignore it for the purposes of annotation processing. 幸运的是,因为它是一个合成方法,你可以通过检查method.isSynthetic()的值来过滤掉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 should work fine. 这应该工作正常。

Update: According to this RFE , annotations should now be copied across to bridge methods as well. 更新:根据此RFE ,现在应将注释复制到桥接方法。

It seems that internally, javac has created 2 methods: 看来在内部, 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
}

The hello(java.lang.Object) method checks the type of the object, and then invokes the MyObject method, which has the annotation on it. hello(java.lang.Object)方法检查对象的类型,然后调用MyObject方法,该方法上有注释。


Update 更新

I see that these additional "bridge methods" are specifically called out as part of type erasure and generics: 我看到这些额外的“桥接方法”被特别称为类型擦除和泛型的一部分:

https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

Also, the annotations missing on these bridged methods is a bug which is fixed in Java 8 u94 此外,这些桥接方法中缺少的注释是一个在Java 8 u94中修复的错误

The method appears twice. 该方法出现两次。 One is annotated and the other one not. 一个是注释而另一个没有注释。 I guess that is your case too but the assertion error happens on the bad one and you can't get to see the good. 我想这也是你的情况,但断言错误发生在坏的一个上,你无法看到好的。

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

Output is: 输出是:

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: As I supossed and others confirmed, he method gets duplicated by the compiler. 编辑:当我上诉和其他人确认,他的方法被编译器复制。 It is needed by java. 它需要java。 I got to decompile it the right way: 我必须以正确的方式反编译

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

It is related to this question: Passing Derived Class to a method which needs to override expecting a base class 它与这个问题有关:将派生类传递给需要覆盖期望基类的方法

I think this is related too. 我认为这也是相关的。 The field is duplicated because the method is: Duplicated field in generated XML using JAXB 该字段是重复的,因为该方法是: 使用JAXB生成的XML中的重复字段

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM