Annotation de l’interface fonctionnelle d’une expression Lambda

Java 8 introduit les expressions Lambda et les annotations de type .

Avec les annotations de type, il est possible de définir les annotations Java comme suit:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) public @interface MyTypeAnnotation { public Ssortingng value(); } 

On peut alors utiliser cette annotation sur n’importe quelle référence de type comme par exemple:

 Consumer consumer = new @MyTypeAnnotation("Hello ") Consumer() { @Override public void accept(Ssortingng str) { System.out.println(str); } }; 

Voici un exemple complet qui utilise cette annotation pour imprimer “Hello World”:

 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedType; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; public class Java8Example { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) public @interface MyTypeAnnotation { public Ssortingng value(); } public static void main(Ssortingng[] args) { List list = Arrays.asList("World!", "Type Annotations!"); testTypeAnnotation(list, new @MyTypeAnnotation("Hello ") Consumer() { @Override public void accept(Ssortingng str) { System.out.println(str); } }); } public static void testTypeAnnotation(List list, Consumer consumer){ MyTypeAnnotation annotation = null; for (AnnotatedType t : consumer.getClass().getAnnotatedInterfaces()) { annotation = t.getAnnotation(MyTypeAnnotation.class); if (annotation != null) { break; } } for (Ssortingng str : list) { if (annotation != null) { System.out.print(annotation.value()); } consumer.accept(str); } } } 

Le résultat sera:

 Hello World! Hello Type Annotations! 

Dans Java 8, on peut également remplacer la classe anonyme dans cet exemple par une expression lambda:

 public static void main(Ssortingng[] args) { List list = Arrays.asList("World!", "Type Annotations!"); testTypeAnnotation(list, p -> System.out.println(p)); } 

Mais comme le compilateur déduit l’argument de type Consumer pour l’expression lambda, il n’est plus possible d’annoter l’instance Consumer créée:

 testTypeAnnotation(list, @MyTypeAnnotation("Hello ") (p -> System.out.println(p))); // Illegal! 

On pourrait convertir l’expression lambda en un consommateur, puis annoter la référence de type de l’expression de dissortingbution:

 testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer) (p -> System.out.println(p))); // Legal! 

Mais cela ne produira pas le résultat souhaité, car la classe Consumer créée ne sera pas annotée avec l’annotation de l’expression de dissortingbution. Sortie:

 World! Type Annotations! 

Deux questions:

  1. Existe-t-il un moyen d’annoter une expression lambda similaire à l’annotation d’une classe anonyme correspondante, alors on obtient la sortie “Hello World” attendue dans l’exemple ci-dessus?

  2. Dans l’exemple, où j’ai converti l’expression lambda et annoté le type de dissortingbution: Existe-t-il un moyen de recevoir cette instance d’annotation au moment de l’exécution ou est-ce que cette annotation est toujours implicitement limitée à RetentionPolicy.SOURCE?

Les exemples ont été testés avec javac et le compilateur Eclipse.

Mettre à jour

J’ai essayé la suggestion de @assylias, d’annoter le paramètre à la place, ce qui a produit un résultat intéressant. Voici la méthode de test mise à jour:

 public static void testTypeAnnotation(List list, Consumer consumer){ MyTypeAnnotation annotation = null; for (AnnotatedType t : consumer.getClass().getAnnotatedInterfaces()) { annotation = t.getAnnotation(MyTypeAnnotation.class); if (annotation != null) { break; } } if (annotation == null) { // search for annotated parameter instead loop: for (Method method : consumer.getClass().getMethods()) { for (AnnotatedType t : method.getAnnotatedParameterTypes()) { annotation = t.getAnnotation(MyTypeAnnotation.class); if (annotation != null) { break loop; } } } } for (Ssortingng str : list) { if (annotation != null) { System.out.print(annotation.value()); } consumer.accept(str); } } 

Maintenant, on peut aussi produire le résultat “Hello World”, lors de l’annotation du paramètre d’une classe anonyme:

 public static void main(Ssortingng[] args) { List list = Arrays.asList("World!", "Type Annotations!"); testTypeAnnotation(list, new Consumer() { @Override public void accept(@MyTypeAnnotation("Hello ") Ssortingng str) { System.out.println(str); } }); } 

Mais l’annotation du paramètre ne fonctionne pas pour les expressions lambda:

 public static void main(Ssortingng[] args) { List list = Arrays.asList("World!", "Type Annotations!"); testTypeAnnotation(list, (@MyTypeAnnotation("Hello ") Ssortingng str) -> System.out.println(str)); } 

Fait intéressant, il n’est pas non plus possible de recevoir le nom du paramètre (lors de la compilation avec javac -parameter) lors de l’utilisation d’une expression lambda. Je ne suis pas sûr si ce comportement est prévu, si les annotations de parameters de lambdas n’ont pas encore été implémentées, ou si cela doit être considéré comme un bogue du compilateur.

Après avoir fouillé dans la spécification finale de Java SE 8, je peux répondre à mes questions.

(1) En réponse à ma première question

Existe-t-il un moyen d’annoter une expression lambda similaire à l’annotation d’une classe anonyme correspondante, alors on obtient la sortie “Hello World” attendue dans l’exemple ci-dessus?

Non.

Lors de l’annotation de l’ Class Instance Creation Expression (§15.9) d’un type anonyme, l’annotation sera stockée dans le fichier de classe pour l’interface d’extension ou la classe d’extension du type anonyme.

Pour l’annotation d’interface anonyme suivante

 Consumer c = new @MyTypeAnnotation("Hello ") Consumer() { @Override public void accept(Ssortingng str) { System.out.println(str); } }; 

Vous pouvez accéder à l’annotation de type au moment de l’ exécution en appelant Class#getAnnotatedInterfaces() :

 MyTypeAnnotation a = c.getClass().getAnnotatedInterfaces()[0].getAnnotation(MyTypeAnnotation.class); 

Si vous créez une classe anonyme avec un corps vide comme celui-ci:

 class MyClass implements Consumer{ @Override public void accept(Ssortingng str) { System.out.println(str); } } Consumer c = new @MyTypeAnnotation("Hello ") MyClass(){/*empty body!*/}; 

Vous pouvez également accéder à l’annotation de type au moment de l’ exécution en appelant Class#getAnnotatedSuperclass() :

 MyTypeAnnotation a = c.getClass().getAnnotatedSuperclass().getAnnotation(MyTypeAnnotation.class); 

Ce type d’annotation de type n’est pas possible pour les expressions lambda.

Sur une note de côté, ce type d’annotation n’est pas non plus possible pour les expressions de création d’instance de classe normales comme ceci:

 Consumer c = new @MyTypeAnnotation("Hello ") MyClass(); 

Dans ce cas, l’annotation de type sera stockée dans la structure method_info de la méthode, où l’expression s’est produite et non comme une annotation du type lui-même (ou de l’un de ses super types).

Cette différence est importante car les annotations stockées dans method_info ne seront pas accessibles à l’exécution par l’API de reflection Java. En regardant le code d’octet généré avec ASM , la différence ressemble à ceci:

Type Annotation sur une création d’instance d’interface anonyme:

 @Java8Example$MyTypeAnnotation(value="Hello ") : CLASS_EXTENDS 0, null // access flags 0x0 INNERCLASS Java8Example$1 

Annotation de type sur une création d’instance de classe normale:

 NEW Java8Example$MyClass @Java8Example$MyTypeAnnotation(value="Hello ") : NEW, null 

Alors que dans le premier cas, l’annotation est associée à la classe interne , dans le second cas, l’annotation est associée à l’expression de création d’instance dans le code d’octet des méthodes.

(2) En réponse au commentaire de @assylias

Vous pouvez également essayer (@MyTypeAnnotation (“Hello”) Ssortingng s) -> System.out.println (s) bien que je n’ai pas réussi à accéder à la valeur d’annotation …

Oui, cela est effectivement possible selon la spécification Java 8. Mais il n’est actuellement pas possible de recevoir les annotations de type des parameters formels des expressions lambda via l’API de reflection Java, qui est probablement liée à ce bogue JDK: Type Annotations Cleanup . De plus, le compilateur Eclipse ne stocke pas encore l’atsortingbut Runtime [In] VisibleTypeAnnotations correspondant dans le fichier de classe – le bogue correspondant est trouvé ici: les noms et les annotations des parameters Lambda ne sont pas associés aux fichiers de classe.

(3) En réponse à ma deuxième question

Dans l’exemple, où j’ai converti l’expression lambda et annoté le type de dissortingbution: Existe-t-il un moyen de recevoir cette instance d’annotation au moment de l’exécution ou est-ce que cette annotation est toujours implicitement limitée à RetentionPolicy.SOURCE?

Lors de l’annotation du type d’une expression de dissortingbution, cette information est également stockée dans la structure method_info du fichier de classe. Il en va de même pour d’autres emplacements possibles des annotations de type dans le code d’une méthode, par exemple if(c instanceof @MyTypeAnnotation Consumer) . Il n’existe actuellement aucune API de reflection Java publique pour accéder à ces annotations de code. Mais comme ils sont stockés dans le fichier de classe, il est au moins possible d’y accéder au moment de l’exécution, par exemple en lisant le code d’octet d’une classe avec une bibliothèque externe comme ASM .

En fait, j’ai réussi à obtenir mon exemple “Hello World” avec une expression comme

 testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer) (p -> System.out.println(p))); 

en analysant le code d’octet des méthodes appelantes à l’aide d’ASM. Mais le code est très pirate et inefficace, et on ne devrait probablement jamais faire quelque chose comme ça dans le code de production. Quoi qu’il en soit, juste pour être complet, voici l’exemple complet “Hello World”:

 import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedType; import java.lang.reflect.Method; import java.net.URL; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.TypePath; import org.objectweb.asm.TypeReference; public class Java8Example { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) public @interface MyTypeAnnotation { public Ssortingng value(); } public static void main(Ssortingng[] args) { List list = Arrays.asList("World!", "Type Annotations!"); testTypeAnnotation(list, new @MyTypeAnnotation("Hello ") Consumer() { @Override public void accept(Ssortingng str) { System.out.println(str); } }); list = Arrays.asList("Type-Cast Annotations!"); testTypeAnnotation(list,(@MyTypeAnnotation("Hello ") Consumer) (p -> System.out.println(p))); } public static void testTypeAnnotation(List list, Consumer consumer){ MyTypeAnnotation annotation = null; for (AnnotatedType t : consumer.getClass().getAnnotatedInterfaces()) { annotation = t.getAnnotation(MyTypeAnnotation.class); if (annotation != null) { break; } } if (annotation == null) { // search for annotated parameter instead loop: for (Method method : consumer.getClass().getMethods()) { for (AnnotatedType t : method.getAnnotatedParameterTypes()) { annotation = t.getAnnotation(MyTypeAnnotation.class); if (annotation != null) { break loop; } } } } if (annotation == null) { annotation = findCastAnnotation(); } for (Ssortingng str : list) { if (annotation != null) { System.out.print(annotation.value()); } consumer.accept(str); } } private static MyTypeAnnotation findCastAnnotation() { // foundException gets thrown, when the cast annotation is found or the search ends. // The found annotation will then be stored at foundAnnotation[0] final RuntimeException foundException = new RuntimeException(); MyTypeAnnotation[] foundAnnotation = new MyTypeAnnotation[1]; try { // (1) find the calling method StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); StackTraceElement previous = null; for (int i = 0; i < stackTraceElements.length; i++) { if (stackTraceElements[i].getMethodName().equals("testTypeAnnotation")) { previous = stackTraceElements[i+1]; } } if (previous == null) { // shouldn't happen return null; } final String callingClassName = previous.getClassName(); final String callingMethodName = previous.getMethodName(); final int callingLineNumber = previous.getLineNumber(); // (2) read and visit the calling class ClassReader cr = new ClassReader(callingClassName); cr.accept(new ClassVisitor(Opcodes.ASM5) { @Override public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) { if (name.equals(callingMethodName)) { // (3) visit the calling method return new MethodVisitor(Opcodes.ASM5) { int lineNumber; String type; public void visitLineNumber(int line, Label start) { this.lineNumber = line; }; public void visitTypeInsn(int opcode, String type) { if (opcode == Opcodes.CHECKCAST) { this.type = type; } else{ this.type = null; } }; public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { if (lineNumber == callingLineNumber) { // (4) visit the annotation, if this is the calling line number AND the annotation is // of type MyTypeAnnotation AND it was a cast expression to "java.util.function.Consumer" if (desc.endsWith("Java8Example$MyTypeAnnotation;") && this.type != null && this.type.equals("java/util/function/Consumer")) { TypeReference reference = new TypeReference(typeRef); if (reference.getSort() == TypeReference.CAST) { return new AnnotationVisitor(Opcodes.ASM5) { public void visit(String name, final Object value) { if (name.equals("value")) { // Heureka! - we found the Cast Annotation foundAnnotation[0] = new MyTypeAnnotation() { @Override public Class annotationType() { return MyTypeAnnotation.class; } @Override public Ssortingng value() { return value.toSsortingng(); } }; // stop search (Annotation found) throw foundException; } }; }; } } } else if (lineNumber > callingLineNumber) { // stop search (Annotation not found) throw foundException; } return null; }; }; } return null; } }, 0); } catch (Exception e) { if (foundException == e) { return foundAnnotation[0]; } else{ e.printStackTrace(); } } return null; } } 

Un travail possible autour de ce qui pourrait être utile est de définir des interfaces vides qui étendent l’interface que le lambda va implémenter, puis jettent dans cette interface vide juste pour utiliser l’annotation. Ainsi:

 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.function.Consumer; public class Main { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) public @interface MyAnnotation { public Ssortingng value(); } @MyAnnotation("Get this") interface AnnotatedConsumer extends Consumer{}; public static void main( Ssortingng[] args ) { printMyAnnotationValue( (AnnotatedConsumer)value->{} ); } public static void printMyAnnotationValue( Consumer consumer ) { Class clas = consumer.getClass(); MyAnnotation annotation = clas.getAnnotation( MyAnnotation.class ); for( Class infClass : clas.getInterfaces() ){ annotation = infClass.getAnnotation( MyAnnotation.class ); System.out.println( "MyAnnotation value: " + annotation.value() ); } } } 

L’annotation est alors disponible sur les interfaces implémentées par la classe et est réutilisable si vous souhaitez la même annotation ailleurs.

 public class Calculator { public static void main(Ssortingng[] args) { try(Scanner scanner=new Scanner(System.in)){ System.out.println("Enter the operation to perform"); Ssortingng key=scanner.next(); int i,j; switch (key) { case "Add": System.out.println("Input 2 values for addtion"); i =scanner.nextInt(); j=scanner.nextInt(); Calculate add=(Integer a, Integer b)-> a+b; System.out.println("After Addition of values="+add.calculate(i, j)); break; case "Multiply": System.out.println("Input 2 values for Multiplication"); i=scanner.nextInt(); j=scanner.nextInt(); Calculate multiplication=(a,b)->a*b; System.out.println("After Multiplication of values="+multiplication.calculate(i, j)); break; case "Subtract": System.out.println("Input 2 values for Subtraction"); i=scanner.nextInt(); j=scanner.nextInt(); Calculate subtract=(a,b)->ab; System.out.println("After Subtraction of values="+subtract.calculate(i, j)); break; case "Division": System.out.println("Input 2 values for Division"); i=scanner.nextInt(); j=scanner.nextInt(); Calculate division=(a,b)->a/b; if(j>0){ System.out.println("After Division of values="+division.calculate(i, j)); }else{ throw new Exception("Second value is 0. Please change the value"); } break; default: break; } }catch(Exception e){ e.printStackTrace(); } } } 

** Utiliser l’interface **

  @FunctionalInterface public interface Calculate { public T calculate(T a,T b); }