Le compilateur de Java ne conserve pas les annotations de méthodes génériques?

Je rencontre actuellement un problème avec les annotations d’effacement de type générique et d’exécution de Java et je ne sais pas si je fais quelque chose de mal ou s’il s’agit d’un bogue du compilateur Java. Prenons l’exemple de travail minimal suivant:

@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface MyAnnotation { } public interface MyGenericInterface { void hello(T there); } public class MyObject { } public class MyClass implements MyGenericInterface { @Override @MyAnnotation public void hello(final MyObject there) { } } 

Maintenant, lorsque je demande des informations sur MyClass.hello avec reflection, je suppose que la méthode hello a toujours l’annotation, mais ce n’est pas le cas:

 public class MyTest { @Test public void testName() throws Exception { Method[] declaredMethods = MyClass.class.getDeclaredMethods(); for (Method method : declaredMethods) { Assert.assertNotNull(Ssortingng.format("Method '%s' is not annotated.", method), method .getAnnotation(MyAnnotation.class)); } } } 

Le message d’erreur (inattendu) se lit comme suit:

java.lang.AssertionError: La méthode ‘public void test.MyClass.hello (java.lang.Object)’ n’est pas annotée.

Testé avec Java 1.7.60.

Comme cela a été souligné par d’autres, la compilation génère deux méthodes du même nom, hello(Object) et hello(MyObject) .

La raison en est le type effacement:

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

Ce qui précède doit être compilé car l’effacement de void hello(T) est void hello(Object) . Bien sûr, cela devrait également échouer au moment de l’exécution car il n’y a pas d’implémentation qui accepterait un Object arbitraire.

D’après ce qui précède, nous pouvons conclure que void hello(MyObject) n’est pas une substitution valide pour cette méthode. Mais les génériques seraient vraiment inutiles si vous ne pouviez pas “écraser” une méthode avec un paramètre de type.

Le compilateur y parvient en générant une méthode synthétique avec la signature void hello(Object) , qui vérifie le type de paramètre d’entrée au moment de l’exécution et est déléguée à void hello(MyObject) si la vérification aboutit. Comme vous pouvez le voir dans le code d’octet dans la réponse de John Farrelly .

Donc, votre classe ressemble vraiment à ceci (observez comment votre annotation rest sur la méthode d’origine):

 public class MyClass implements MyGenericInterface { @MyAnnotation public void hello(final MyObject there) { } @Override public void hello(Object ob) { hello((MyObject)ob); } } 

Heureusement, puisqu’il s’agit d’une méthode synthétique, vous pouvez filtrer void hello(Object) en vérifiant la valeur de method.isSynthetic() . Si c’est le cas, vous devez simplement l’ignorer aux fins du traitement des annotations.

 @Test public void testName() throws Exception { Method[] declaredMethods = MyClass.class.getDeclaredMethods(); for (Method method : declaredMethods) { if (!method.isSynthetic()) { Assert.assertNotNull(Ssortingng.format("Method '%s' is not annotated.", method), method .getAnnotation(MyAnnotation.class)); } } } 

Cela devrait bien fonctionner.

Mise à jour: selon cet appel à propositions , les annotations doivent maintenant être copiées afin de permettre le rapprochement des méthodes.

Il semble qu’en interne, javac a créé 2 méthodes:

 $ javap -c MyClass.class Comstackd from "MyTest.java" class MyClass implements MyGenericInterface { MyClass(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()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 } 

La méthode hello(java.lang.Object) vérifie le type de l’object, puis appelle la méthode MyObject , qui comporte l’annotation.


Mettre à jour

Je vois que ces “méthodes de pont” supplémentaires sont spécifiquement appelées dans le cadre de l’effacement de types et des génériques:

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

De plus, les annotations manquantes sur ces méthodes pontées sont un bogue corrigé dans Java 8 u94

La méthode apparaît deux fois. L’un est annoté et l’autre non. Je suppose que c’est aussi votre cas, mais l’erreur d’assertion se produit sur le mauvais et vous ne pouvez pas voir le bon.

 Method[] declaredMethods = MyClass.class.getDeclaredMethods(); for (Method method : declaredMethods) { System.out.println(Ssortingng.format("Method: %s", method)); for (Annotation a: method.getAnnotations()) { System.out.println(Ssortingng.format(" Annotation: %s of class %s", a, a.annotationType())); } for (Annotation a: method.getDeclaredAnnotations()) { System.out.println(Ssortingng.format(" DeclaredAnnotation: %s of class %s", a, a.annotationType())); } if (method.getDeclaredAnnotation(MyAnnotation.class) == null) { System.out.println(Ssortingng.format( " Method '%s' is not annotated.", method)); } } 

La sortie est:

 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: Comme je l’ai lu et confirmé par d’autres, sa méthode est dupliquée par le compilateur. Java en a besoin. Je dois le décomstackr de la bonne façon:

 //# java -jar ........\cfr_0_101.jar MyClass --hidebridgemethods false /* * Decomstackd with CFR 0_101. */ import MyAnnotation; import MyGenericInterface; import MyObject; public class MyClass implements MyGenericInterface { @MyAnnotation @Override public void hello(MyObject there) { } @Override public /* bridge */ /* synthetic */ void hello(Object object) { MyClass myClass; myClass.hello((MyObject)object); } } 

Elle est liée à cette question: Passing Derived Class à une méthode qui doit remplacer une classe de base attendue.

Je pense que cela est lié aussi. Le champ est dupliqué car la méthode est la suivante: Champ dupliqué en XML généré à l’aide de JAXB