Vous avez probablement rencontré des situations où vous avez besoin d'associer métadonnées (données décrivant d'autres données) avec des classes, des méthodes et/ou d'autres éléments d'application. Par exemple, votre équipe de programmation peut avoir besoin d'identifier des classes inachevées dans une application volumineuse. Pour chaque classe inachevée, les métadonnées incluraient probablement le nom du développeur responsable de la fin de la classe et la date d'achèvement prévue de la classe.
Avant Java 5, les commentaires étaient le seul mécanisme flexible que Java pouvait proposer pour associer des métadonnées à des éléments d'application. Cependant, les commentaires constituent un mauvais choix. Étant donné que le compilateur les ignore, les commentaires ne sont pas disponibles au moment de l'exécution. Et même s'ils l'étaient, le texte devrait être analysé pour obtenir des éléments de données cruciaux. Sans normalisation de la manière dont les éléments de données sont spécifiés, ces éléments de données pourraient s'avérer impossibles à analyser.
Téléchargez le code source des exemples de ce didacticiel Java 101. Créé par Jeff Friesen pour InfoWorld.
Java 5 a tout changé en introduisant annotationsun mécanisme standard permettant d'associer des métadonnées à divers éléments d'application. Ce mécanisme se compose de quatre éléments :
- Un
@interface
mécanisme de déclaration des types d'annotations. - Types de méta-annotations, que vous pouvez utiliser pour identifier les éléments d'application auxquels s'applique un type d'annotation ; pour identifier la durée de vie d'un annotation (une instance d'un type d'annotation) ; et plus encore.
- Prise en charge du traitement des annotations via une extension de l'API Java Reflection (à discuter dans un prochain article), que vous pouvez utiliser pour découvrir les annotations d'exécution d'un programme, et un outil généralisé pour le traitement des annotations.
- Types d'annotations standards.
J'expliquerai comment utiliser ces composants au fur et à mesure que nous progressons dans cet article.
Déclaration des types d'annotations avec @interface
Vous pouvez déclarer un type d’annotation en spécifiant le @
symbole immédiatement suivi du interface
mot réservé et un identifiant. Par exemple, la liste 1 déclare un type d'annotation simple que vous pouvez utiliser pour annoter du code thread-safe.
Liste 1 : ThreadSafe.java
public @interface ThreadSafe { }
Après avoir déclaré ce type d'annotation, préfixez les méthodes que vous considérez comme thread-safe avec des instances de ce type en ajoutant @
immédiatement suivi du nom du type dans les en-têtes de méthode. Le listing 2 offre un exemple simple où le main()
la méthode est annotée @ThreadSafe
.
Liste 2 : AnnDemo.java
(version 1)
public class AnnDemo { @ThreadSafe public static void main(String() args) { } }
ThreadSafe
Les instances ne fournissent aucune métadonnée autre que le nom du type d'annotation. Cependant, vous pouvez fournir des métadonnées en ajoutant des éléments à ce type, où un élément est un en-tête de méthode placé dans le corps du type d'annotation.
En plus de ne pas avoir de corps de code, les éléments sont soumis aux restrictions suivantes :
- L'en-tête de méthode ne peut pas déclarer de paramètres.
- L'en-tête de méthode ne peut pas fournir de clause throws.
- Le type de retour de l'en-tête de méthode doit être un type primitif (par exemple,
int
),java.lang.String
,java.lang.Class
une énumération, un type d'annotation ou un tableau de l'un de ces types. Aucun autre type ne peut être spécifié pour le type de retour.
À titre d’exemple, la liste 3 présente une ToDo
type d'annotation avec trois éléments identifiant un travail de codage particulier, spécifiant la date à laquelle le travail doit être terminé et nommant le codeur responsable de l'achèvement du travail.
Liste 3 : ToDo.java
(version 1)
public @interface ToDo { int id(); String finishDate(); String coder() default "n/a"; }
Notez que chaque élément ne déclare aucun paramètre ou ne renvoie aucune clause et possède un type de retour légal (int
ou String
) et se termine par un point-virgule. De plus, l'élément final révèle qu'une valeur de retour par défaut peut être spécifiée ; cette valeur est renvoyée lorsqu'une annotation n'attribue pas de valeur à l'élément.
Liste de 4 utilisations ToDo
pour annoter une méthode de classe inachevée.
Liste 4 : AnnDemo.java
(version 2)
public class AnnDemo { public static void main(String() args) { String() cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(id = 1000, finishDate = "10/10/2019", coder = "John Doe") static void sort(Object() objects) { } }
La liste 4 attribue un élément de métadonnées à chaque élément ; par exemple, 1000
est affecté à id
. Contrairement à coder
le id
et finishDate
les éléments doivent être spécifiés ; sinon, le compilateur signalera une erreur. Lorsque coder
aucune valeur n'est attribuée, elle assume sa valeur par défaut "n/a"
valeur.
Java fournit une solution spéciale String value()
élément qui peut être utilisé pour renvoyer une liste d'éléments de métadonnées séparés par des virgules. La liste 5 illustre cet élément dans une version refactorisée de ToDo
.
Liste 5 : ToDo.java
(version 2)
public @interface ToDo { String value(); }
Quand value()
est le seul élément d'un type d'annotation, vous n'avez pas besoin de le spécifier value
et le =
opérateur d'affectation lors de l'affectation d'une chaîne à cet élément. La liste 6 illustre les deux approches.
Liste 6 : AnnDemo.java
(version 3)
public class AnnDemo { public static void main(String() args) { String() cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(value = "1000,10/10/2019,John Doe") static void sort(Object() objects) { } @ToDo("1000,10/10/2019,John Doe") static boolean search(Object() objects, Object key) { return false; } }
Utilisation des types de méta-annotations : le problème de la flexibilité
Vous pouvez annoter des types (par exemple, des classes), des méthodes, des variables locales, etc. Cependant, cette flexibilité peut être problématique. Par exemple, vous souhaiterez peut-être restreindre ToDo
aux méthodes uniquement, mais rien n'empêche de l'utiliser pour annoter d'autres éléments d'application, comme le montre le Listing 7.
Liste 7 : AnnDemo.java
(version 4)
@ToDo("1000,10/10/2019,John Doe") public class AnnDemo { public static void main(String() args) { @ToDo(value = "1000,10/10/2019,John Doe") String() cities = { "New York", "Melbourne", "Beijing", "Moscow", "Paris", "London" }; sort(cities); } @ToDo(value = "1000,10/10/2019,John Doe") static void sort(Object() objects) { } @ToDo("1000,10/10/2019,John Doe") static boolean search(Object() objects, Object key) { return false; } }
Dans la liste 7, ToDo
est également utilisé pour annoter le AnnDemo
classe et cities
variable locale. La présence de ces annotations erronées peut dérouter quelqu'un qui examine votre code, ou même vos propres outils de traitement d'annotations. Pour les moments où vous devez réduire la flexibilité d'un type d'annotation, Java offre la Target
type d'annotation dans son java.lang.annotation
emballer.
Target
est un type de méta-annotation — un type d'annotation dont les annotations annotent les types d'annotation, par opposition à un type d'annotation non méta dont les annotations annotent les éléments d'application, tels que les classes et les méthodes. Il identifie les types d'éléments d'application auxquels un type d'annotation est applicable. Ces éléments sont identifiés par Target
's ElementValue() value()
élément.
java.lang.annotation.ElementType
est une énumération dont les constantes décrivent les éléments de l'application. Par exemple, CONSTRUCTOR
s'applique aux constructeurs et PARAMETER
s'applique aux paramètres. Liste 8 refactorisations Liste 5 ToDo
type d'annotation pour le restreindre aux méthodes uniquement.
Liste 8 : ToDo.java
(version 3)
import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.METHOD}) public @interface ToDo { String value(); }
Étant donné le refactorisé ToDo
type d'annotation, une tentative de compilation du listing 7 génère désormais le message d'erreur suivant :
AnnDemo.java:1: error: annotation type not applicable to this kind of declaration @ToDo("1000,10/10/2019,John Doe") ^ AnnDemo.java:6: error: annotation type not applicable to this kind of declaration @ToDo(value="1000,10/10/2019,John Doe") ^ 2 errors
Types de méta-annotations supplémentaires
Java 5 a introduit trois types de méta-annotations supplémentaires, qui se trouvent dans le java.lang.annotation
emballer:
Retention
indique la durée de conservation des annotations avec le type annoté. Ce type est associéjava.lang.annotation.RetentionPolicy
enum déclare des constantesCLASS
(le compilateur enregistre les annotations dans le fichier de classe ; la machine virtuelle ne les conserve pas pour économiser de la mémoire — politique par défaut),RUNTIME
(le compilateur enregistre les annotations dans le fichier de classe ; la machine virtuelle les conserve), etSOURCE
(le compilateur supprime les annotations).Documented
indique que les cas deDocumented
-les annotations annotées doivent être documentées parjavadoc
et des outils similaires.Inherited
indique qu'un type d'annotation est automatiquement hérité.
Java 8 a introduit le java.lang.annotation.Repeatable
type de méta-annotation. Repeatable
est utilisé pour indiquer que le type d'annotation dont il (méta-)annote la déclaration est répétable. En d'autres termes, vous pouvez appliquer plusieurs annotations du même type d'annotation répétable à un élément d'application, comme illustré ici :
@ToDo(value = "1000,10/10/2019,John Doe") @ToDo(value = "1001,10/10/2019,Kate Doe") static void sort(Object() objects) { }
Cet exemple suppose que ToDo
a été annoté avec le Repeatable
type d'annotation.
Traitement des annotations
Les annotations sont censées être traitées ; sinon, elles n'ont aucun intérêt. Java 5 a étendu l'API Reflection pour vous aider à créer vos propres outils de traitement d'annotations. Par exemple, Class
déclare un Annotation()
méthode qui renvoie un tableau de
getAnnotations()java.lang.Annotation
instances décrivant les annotations présentes sur l'élément décrit par le Class
objet.
Le listing 9 présente une application simple qui charge un fichier de classe, interroge ses méthodes pour ToDo
annotations et génère les composants de chaque annotation trouvée.
Liste 9 : AnnProcDemo.java
import java.lang.reflect.Method; public class AnnProcDemo { public static void main(String() args) throws Exception { if (args.length != 1) { System.err.println("usage: java AnnProcDemo classfile"); return; } Method() methods = Class.forName(args(0)).getMethods(); for (int i = 0; i < methods.length; i++) { if (methods(i).isAnnotationPresent(ToDo.class)) { ToDo todo = methods(i).getAnnotation(ToDo.class); String() components = todo.value().split(","); System.out.printf("ID = %s%n", components(0)); System.out.printf("Finish date = %s%n", components(1)); System.out.printf("Coder = %s%n%n", components(2)); } } } }
Après avoir vérifié qu'un seul argument de ligne de commande (identifiant un fichier de classe) a été spécifié, main()
charge le fichier de classe via Class.forName()
invoque getMethods()
pour renvoyer un tableau de java.lang.reflect.Method
objets identifiant tous public
méthodes dans le fichier de classe et traite ces méthodes.
Le traitement de la méthode commence par l'invocation Method
's boolean isAnnotationPresent(Class extends
méthode pour déterminer si l'annotation décrite par
Annotation> annotationClass)ToDo.class
est présent sur la méthode. Si c'est le cas, Method
's
la méthode est appelée pour obtenir l'annotation.
annotationClass)
Le ToDo
les annotations traitées sont celles dont les types déclarent un seul String value()
élément (voir Listing 5). Étant donné que les métadonnées basées sur la chaîne de cet élément sont séparées par des virgules, elles doivent être divisées en un tableau de valeurs de composants. Chacune des trois valeurs de composants est ensuite consultée et générée.
Compilez ce code source (javac AnnProcDemo.java
). Avant de pouvoir exécuter l'application, vous aurez besoin d'un fichier de classe approprié avec @ToDo
annotations sur son public
méthodes. Par exemple, vous pouvez modifier la liste 6 AnnDemo
code source à inclure public
dans son sort()
et search()
en-têtes de méthode. Vous aurez également besoin du Listing 10 ToDo
type d'annotation, qui nécessite le RUNTIME
politique de conservation.
Liste 10 : ToDo.java
(version 4)
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ToDo { String value(); }
Compiler le modifié AnnDemo.java
et la liste 10, et exécutez la commande suivante pour traiter AnnDemo
's ToDo
annotations:
java AnnProcDemo AnnDemo
Si tout se passe bien, vous devriez observer le résultat suivant :
ID = 1000 Finish date = 10/10/2019 Coder = John Doe ID = 1000 Finish date = 10/10/2019 Coder = John Doe
Types d'annotations standards
Avec Target
, Retention
, Documented
et Inherited
Java 5 introduit java.lang.Deprecated
, java.lang.Override
et java.lang.SuppressWarnings
Ces trois types d'annotations sont conçus pour être utilisés uniquement dans un contexte de compilateur, c'est pourquoi leurs politiques de conservation sont définies sur SOURCE
.
GIPHY App Key not set. Please check settings