Les annotations

Chapitres traités   

Les annotations permettent d'associer des informations arbitraires, ou meta-données, aux éléments d'un programme. Syntaxiquement, les annotations s'utilisent comme des modificateurs et peuvent s'appliquer aus déclarations de paquetages, aux types, aux constructeurs, aux méthodes, aux attributs, aux paramètres et aux variables locales. Les informations enregistrées dans une annotation prennent la forme de paires (nom=valeur) au type spécifié par le type d'annotation. Le type d'annotation est une forme d'interface qui permet également d'accéder à l'annotation avec l'API de réflexion Java.

Les annotations permettent d'associer n'importe quel type d'information à un élément de programme. La seule règle fondamentale est qu'une association ne peut pas affecter l'exécution du programme : le code doit s'exécuter de la même manière, que des annotations s'y trouve ou pas. Autrement dit, l'interpréteur Java ignore les annotations (bien qu'il rende les annotations "visibles à l'exécution" disponibles pour un accès réflexif via l'API de réflexion Java). Comme la Machine virtuelle Java ignore les annotations, un type d'annotation n'est utile qu'avec un outil d'accompagnement capable de traiter les informations enregistrées dans les annotations de ce type.

Il est facile d'imaginer de nombreuses utilisations pour les annotations. Par exemple, une variable locale peut être annotée avec un type nommé NonNull, pour définir l'assertion selon laquelle la variable ne devrait jamais posséder la valeur null. Un outil d'analyse de code pourrait alors analyser le code et tenter de vérifier cette hypothèse. Le kit de développement de Java inclut un outil nomé apt (pour Annotation Process Tool, c'est-à-dire outil de traitement des annotations) qui offre un cadre générique pour les outils des traitements des annotations ; apt parcourt le code source à l'affut d'annotations et invoque des classes de traitement des annotations que vous lui fournissez.

Les annotations peuvent avoir plusieurs autres utilisations possibles :

  1. La génération automatique de fichiers auxiliaires, comme les descripteurs de déploiement ou les classes d'information de beans ;
  2. La mise en oeuvre beaucoup plus facile des Entreprises Java Beans ;
  3. La génération automatique de code pour le test, la consignation, la sémantique de transaction, etc.

 

Choix du chapitre Ajout de métadonnées aux programmes

Terminologies propres aux annotations

Les termes suivants sont fréquemment utilisés dans le cadre des annotations. Il est important de faire la distinction entre eux, notamment entre annotation et type d'annotation.

annotation
Une annotation associe des informations arbitraires, ou méta-données, à un élément de programme Java. Les annotations utilisent une nouvelle syntaxe introduite dans Java 5.0 et se comportent comme les modificateurs public ou final. Chaque annotation possède un nom et zéro ou plusieurs membres. Chaque membre possède un nom ou une valeur et ces paires nom=valeur contiennent des informations de l'annotation.
type d'annotation
Le nom de l'annotation, ainsi que les noms, types, et valeurs par défaut de ses membres, sont définis dans le type d'annotation. Un type d'annotation est essentiellement une interface Java avec quelques restrictions sur ses membres et une nouvelle syntaxe utilisée dans sa déclaration. Lorsque vous extrayer une annotation en utilisant l'API de réflexion Java, la valeur retournée représente un objet qui implémente l'interface du type d'annotation et permet de récupérer les membres individuels de l'annotation.
membre d'annotation
Les membres d'une annotation sont déclarés au sein du type d'anntation sous forme de méthodes sans argument. Le nom de méthode et le type de retour définissent le nom et le type du membre. Une syntaxe default particulière permet de déclarer une valeur par défaut pour chaque membre d'annotation. Une annotation associée à un élément de programme inclut des paires nom=valeur qui définissent les valeurs de tous les membres d'annotation qui ne posèdent pas de valeur par défaut, ainsi que les valeurs redéfinissant les valeurs par défaut d'autres membres.
annotation de marquage
Un type d'annotation qui ne définit pas de membre s'appelle une annotation de marquage. L'information d'une annotation de ce type se limite à sa seule présence ou absence.
méta-annotation
Une méta-annotation est une annotation appliquée à la déclaration d'un type d'annotation. Java 5.0 introduit plusieurs types de méta-annotations standard dans le paquetage java.lang.annotation. Les méta-annotations sont utilisées pour spécifier des informations telles que les éléments auxquels l'annotation peut s'appliquer.
cible
La cible d'une annotation est l'élément de programme annoté. Les annotations peuvenet s'appliquer aux paquetages, aux types, aux membres de ces types, aux paramètres de méthodes et aux varaibles locales. La déclaration d'un type d'annotation peut inclure une méta-a-annotation qui restreint les cibles autorisées par ce type.
rétention
La rétention d'une annotation indique la durée pendant laquelle l'information contenue dans l'annotation est retenue. Certaines annotations sont écartées par le compilateur et n'appaissent pas dans le code source. D'autres sont compilées dans le fichier de classe. De celles qui sont compilées dans le fichier de classe, certaines sont ignorées par la machine virtuelle, alors que d'autres sont lues lors du chargement de la classe. La déclaration d'un type d'annotation peut utiliser une méta-annotation afin de spécifier la rétention des annotations de ce type. Les annotations chargées par la machine virtuelle sont visibles à l'exécution et peuvent être extraites par l'API de réflexion java.lang.reflect.
méta-données
Dans le cadre des annotations, le terme méta-données fait communément référence à l'annotation elle-même, ou aux informations véhiculées par l'annotation.
 
 

Les métadonnées sont des données s'appliquant à d'autres données. Dans le contexte de la programmation, il s'agit de données qui concernent du code. nous pouvons citer comme exemple les commentaires en Javadoc. Ceux-ci décrivent le code mais ne modifient pas sa signification.

En java, une annotation s'utilise comme un modificateur et se place avant l'élément annoté, sans point virgule (un modificateur est un mot clé comme public ou static). Le nom de chaque annotation est précédé du symbole @, comme les commentaires Javadoc. Toutefois, les commentaires Javadoc sont insérés entre les délimiteurs /** et */, tandis que les annotations font partie du code.

public class MaClasse {
   ...
   @Test public void uneMéthode() {
   ...
   }
}

En elle-même, l'annotation @Test ne fait rien. Elle a besoin d'un outil pour être utile. Un outil de test, par exemple, pourrait appeler toutes les méthodes étiquetées @Test lorsqu'il teste une classe. Un autre outil pourrait supprimer toutes les méthodes de test d'un fichier de classe de sorte qu'elles ne soient pas livrées avec le programme de test.

Les annotations peuvent être définies de manière à avoir des éléments comme ceci :

@Test(identification = "3352567")

Outre les méthodes, vous pouvez annoter des classes, des attributs et des variables locales ; une annotation peut se trouver n'importe où, tant que vous pouvez placer un modificateur comme public ou static.

 

Interface d'annotation

Chaque annotation doit être définie par une interface d'annotation. Les méthodes de l'interface correspondent aux éléments de l'annotation. Par exemple, une annotation Test pourrait être définie par l'interface suivante :

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)

public @interface Test {
   String identification() default "[none]";
}

La déclaration @interface crée une vrai interface Java. Les outils de traitement des annotations reçoivent des objets qui implémentent l'interface d'annotation. Un outil appelerait la méthode identification() pour récupérer l'élément d'identification d'une annotation Test particulière.

Les annotations Target et Retention sont des meta-annotations. Elles annotent l'annotation Test, en la marquant comme une annotation applicable aux méthodes seulement et concervée lorsque le fichier de classe est chargée dans la machine virtuelle.

 

Choix du chapitre Syntaxe des annotations

Type d'annotation

Pour utiliser une annotation, nous devons déclarer d'abord le type de l'annotation. Le type d'annotation se définit par une interface :

modificateurs @interface NomAnnotation
{
  déclaration élément 1
  déclaration élément 2
  ...
}

Chaque déclaration d'élément prend la forme :

type NomElément();

ou

type NomElément() default valeur;

Par exemple, l'annotation RapportErreur suivante possède deux éléments, assignéA et importance :

public @interface RapportErreur
{
  String assignéA() default "[Personne]" ;
  int importance() = 0 ;
}

Le type d'un élément peut également être un tableau :

public @interface RapportErreur
{
  String[] rapportéPar() ;
  int importance() = 0 ;
}

Une annotation peut ne pas posséder d'éléments. Dans ce cas particulier, l'annotation est appelée annotation de marquage :

public @interface Instable {}

Nous avons souvent besoin également de définir un type d'annotation avec un membre unique. En nommant ce membre value, nous activons un raccourci syntaxique pour les utilisateurs de l'annotation :

public @interface TypeErreur
{
  String value() ;
}

Nous pouvons alors écrire les annotations sous la forme :

@TypeErreur("Compilation")

au lieu de :

@TypeErreur(value="Compilation")

 

Contraintes sur les types d'annotation

Un type d'annotation est une interface un peu spéciale, qui se distingue d'une interface traditionnelle sur les points suivants :

  1. Un type d'annotation est défini avec le mot clé @interface plutôt que interface. Une déclaration @interface étend implicitement l'interface java.lang.annotation.Annotation et n'a pas forcément sa propre clause extends.
  2. Les méthodes d'un type d'annotation doievent être déclarées sans argument et ne peuvent lever aucune exception. Ces méthodes définissent les membres de l'annotation : le nom de la méthode devient le nom du membre et le type de retour de la méthode devient le type du membre.
  3. La valeur de retour des méthodes d'annotations peut être un type primitif, un objet String ou Class, un type énuméré, un autre type d'annotation ou un tableau unidimentionnel d'un des types précités.
  4. Toute méthode d'un type d'annotation peut être suivie du mot clé default et d'une valeur compatible avec le type de retour de la méthode. Cette nouvelle syntaxe, quoique étrange, spécifie la valeur par défaut du membre d'annotation correspondant à la méthode. La syntaxe des valeur par défaut est la même que la syntaxe utilisée pour spécifier les valeurs des membres lorsque vous écrivez une annotation. La valeur null ne peut jamais être utilisée par défaut.
  5. Les types d'annotation et leurs méthodes ne peuvent pas avoir de paramètres de type - les types d'annotation et leur membres ne peuvent pas être génériques. La seule utilisation autorisée des génériques dans les types d'annotation s'applique aux méthodes dont le type de retour est Class. Ces méthodes peuvent utiliser un joker borné pour spécifier une contrainte sur la classe retournée.

Pour le reste, les types d'annotation déclarés avec @interface sont semblables aux interfaces traditionnelles. Ils peuvent inclurent des définitions de constantes et des types de membres statiques, tels que les définitions de type énumérés. Les types d'annotation peuvent aussi être implémentés ou étendus, tout comme les interfaces normales (par contre, les classes et interfaces résultantes ne sont pas elles-mêmes des types annotation : les types d'annotation peuvent uniquement être créés avec une déclaration @interface).

 

Annotation

Une fois que le type d'annotation est défini, nous pouvons ensuite utiliser les annotations. Une annotation se compose du caractère @ suivi par le nom du type d'annotation (pouvant inclure un nom de paquetage), puis par une liste entre parenthèses de paires nom=valeur séparées par des virgules. Il existe une paire pour chaque membre défini par le type d'annotation. Les membres peuvent apparaître dans n'importe quel ordre et être omis si le type d'annotation leur attribue une valeur par défaut. Chaque valeur doit être une constante à la compilation, une annotation emboîtée ou un tableau.

Chaque annotation a donc le format suivant :

@NomAnnotation(NomElément1=valeur1, NomElément2=valeur2, ...)

Par exemple :

@RapportErreur(assignéA="Manu", importance=10)

L'ordre des éléments importe peu. L'annotation suivante est identique à la précédente :

@RapportErreur(importance=10, assignéA="Manu")

La valeur par défaut de la déclaration est utilisée si une valeur d'élément n'est pas spécifiée. Par exemple, voyez l'annotation suivante :

@RapportErreur(importance=10)

La valeur de l'élément assignéA correspond à la chaîne "[Personne]".

Deux raccourcis permettent de simplifier les annotations :

  1. Annotation de marquage ;
  2. Annotation à valeur unique.

 

Annotation de marquage

Lorsqu'aucun élément n'est spécifié, soit parce que l'annotation n'en a pas, soit parce que tous utilisent la valeur par défaut, vous n'avez pas besoin d'utiliser de parenthèses. Par exemple :

@RapportErreur

équivaut à :

@RapportErreur(assignéA="[Personne]", importance=0)

Cette annotation est dite annotation marqueur ou annotation de marquage.

 

Annotation à valeur unique

L'autre raccourci est l'annotation de valeur unique. Si un élément posssède le nom spécial value et qu'aucun autre élément n'est spécifié, vous pouvez omettre le nom de l'élément et le symbole =. Par exemple, si nous avions défini l'interface de l'annotation TypeErreur sous la forme :

public @interface TypeErreur
{
  String value() ;
}

Nous pouvons alors écrire les annotations sous la forme :

@TypeErreur("Compilation")

au lieu de :

@TypeErreur(value="Compilation")

 

Elément de type tableau

Lorsqu'une valeur d'élément correspond à un tableau, entourez ses valeurs entre accolades, comme ceci :

@RapportErreur(rapportéePar={"Manu", "Emmanuel"} , importance=25)

Vous pouvez omettre les accolades si l'élément possède une seule valeur :

@RapportErreur(rapportéePar="Emmanuel" , importance=25)

 

Une seule annotation par élément

Une autre règle importante dans la syntaxe des annotations est qu'aucun élément de programme ne peut contenir plus d'une instance de la même annotation. Toutefois, un élément peut contenir plusieurs annotations, à condition qu'elles appartiennent à des types différents.

Vous obtenez une erreur de compilation dans le scénario ci-dessous :

@RapportErreur(importance=10, assignéA="Manu")
@RapportErreur(assignéA="Emmanuel")
public void uneMéthode();

Si vous désirez malgré tout proposer plusieurs annotations de même type sur un même élément, vous devez plutôt proposer alors une annotation qui est, en fait, un tableau d'annotations, comme ceci :

@RapportErreurs({ //annotation à valeur unique; "value=" est donc omis
   @RapportErreur(importance=10, assignéA="Manu")
   @RapportErreur(assignéA="Emmanuel")
})
public void uneMéthode();

Voici le type d'annotation correspondant :

public @interface RapportErreurs
{
  RapportErreur[] value() ;
}

 

Types et valeurs des membres d'une annotation

Les valeurs des membres d'une annotation doivent être des expressions constantes à la compilation, non nulles et compatibles avec le type déclaré du membre.

Les types de membre autorisés sont les suivant :

  1. Un type primitif (int, short, long, byte, char, double, float ou boolean) ;
  2. String ;
  3. Class (avec un paramètre de type optionnel comme Class<? extends MaClasse>) ;
  4. un type enum ;
  5. un type d'annotation ;
  6. un tableau des types qui précèdent. (par contre pas de tableau de tableaux)

Par exemple, les expressions 2*Math.PI et "bonjour"+"salut" sont des valeurs autorisées respectivement pour les membres de type double et String.

 

Cible des annotations

Les annotations sont le plus couramment placées sur des définitions de types et leurs membres. Les annotations peuvent apparaître sur les paquetages, les paramètres et les variables locales. Voici recensées les cibles sur lesquelles vous pouvez placer des annotations :

  1. paquetages ;
  2. classes (y compris enum) ;
  3. interfaces ( y compris les interfaces d'annotation) ;
  4. méthodes ;
  5. constructeurs ;
  6. attributs classiques et constants (y compris les constantes enum) ;
  7. variables locales ;
  8. variables de paramètres.

 

Choix du chapitre Annotations standard

Le JDK 5.0 définit sept interfaces d'annotation. Trois d'entre elles sont des annotations ordinaires que vous pouvez utiliser pour annoter des éléments de votre code source. Les quatres autres sont des méta-annotations qui décrivent le comportement des interfaces d'annotation.

type d'annotation Applicable à Objet
Deprecated Tous. Désigne un élément comme obsolète.
SuppressWarning Tous sauf les paquetages et les annotations. Supprime les avertissements du type donné.
Override Méthodes. Vérifie que cette méthode surcharge une méthode de la superclasse.
Target Annotations. Spécifie les éléments auxquels cette annotation peut être appliquée.
Retention Annotations. Spécifie la durée pendant laquelle cette annotation est concervée.
Documented Annotations. Indique que cette annotation doit être incluse dans la documentation des éléments annotés.
Inherited Annotations. Indique que cette annotation, lorsqu'elle est appliquée à une classe, est automatiquement héritée à ses sous-classes.

 

Override

java.lang.Override est un type d'annotation de marquage qui ne peut être utilisé que pour annoter des méthodes. Une annotation de ce type indique que la méthode annotée redéfinit une méthode d'une super-classe. Si vous utilisez cette annotation sur une méthode qui, en réalité, ne redéfinit pas une méthode d'une super-classe, le compilateur génère une erreur pour vous informer.

Cette annotation est conçue pour résoudre une erreur de programmation courante qui se produit lorsque vous tentez de redéfinir une méthode d'une super-classe, mais faites erreur dans le nom ou la signature de cette méthode. Dans ce dernier cas, vous surchargez le nom de la méthode, mais ne la redéfinissez pas. Par conséquent, votre code ne sera jamais invoqué.

Pour utiliser ce type d'annotation, ajoutez simplement @Override aux modificateurs de la méthdoe concernée. Par convention, @Override vient avant tous les autres modificateurs. Toujours par convention, il n'y a pas d'espace entre le caractère @ et le nom Override., bien que les deux formes soient acceptées. Sachant que la paquetage java.lang est toujours importé de façon automatique, vous n'aurez jamais à inclure le nom du paquetage pour utiliser ce type d'annotation.

Voici un exemple dans lequel l'annotation @Override est utilisée sur une méthode qui ne redéfinit pas correctement la méthode toString() de la super-classe :

public class Complexe {
   private double réel;
   private double imaginaire;
   
   public Complexe(double r, double i) {
      réel = r;
      imaginaire = i;
   }
   @Override public String toSting() { 
      // attention, faute de frappe!
       return "["+réel+", "+imaginaire+"]";
   }
}

Sans annotation, cette coquille ne serait pas détectée et nous serions face à un bogue curieux : pourquoi la méthode toString() ne fonctionne-t-elle pas correctement ? Avec l'annotation, par contre, le compilateur nous donne la réponse : la méthode toString() ne fonctionne pas comme prévu car elle n'est pas redéfinie.

 

Deprecated

L'annotation @Deprecated peut être attachée à tout élément dont l'utilisation n'est plus conseillée. Si vous annotez un type ou un membre de type avec @Deprecated, le compilateur saura et vous avertira que l'utilisation de l'élément annoté est découragée. Cette annotation joue le même rôle que la balise javadoc @deprecated.

Notez que le type d'annotation @Deprecated ne rend pas obsolète la balise javadoc @deprecated. L'annotation @Deprecated est destinée au compilateur Java. La balise javadoc, par contre, est destinée à l'outil javadoc, et a un rôle de documentation : elle peut inclure une description expliquant pourquoi l'élément du programme a été rendu obsolète et par quoi il est remplacé.

 

SuppressWarnings

L'annotation @SuppressWarnings est utilisée pour désactiver de façon ponctuelle les avertissements du compilateur concernant les classes, les méthodes ou les initialisateurs d'attributs ou de variables. SuppressWarnings n'est pas une annotation de marquage. Elle possède un membre unique nommé value, de type String[] (nous pouvons donc utiliser la syntaxe simplifiée).

L'annotation @SuppressWarnings indique au compilateur de supprimer les avertissements d'un ou plusieurs types paticuliers :

@SuppressWarnings("unchecked cast")

 

Choix du chapitre Méta-annotations

Les types d'annotation peuvent eux-mêmes être annotés. Java 5.0 définit quatre types de méta-annotations standard, lesquels donnenet des informations sur l'utilisation et la signification d'autres types d'annotation. Ces types et leurs classes correspondantes se trouvent dans le paquetage java.lang.annotation.

Target

Le type de méta-annotation @Target spécifie les "cibles" d'un type d'annotation. Autrement dit, il indique quels éléments de programme peuvent être annotés avec ce type. Si un type d'annotation ne possède pas de méta-annotations @Taget, il peut être utilisé avec tous les éléments de programme précédemment décrits. Certains types d'annotation, pourtant, n'ont de sens qu'appliqués à des éléments de programme bien précis. @Override en est un exemple : ce type n'a de sens qu'appliqué à une méthode. Une méta-annotation @Target appliquée à la déclaration du type @Override rend cette information explicite et permet au compilateur de rejeter un @Override lorsqu'il apparaît dans un contexte inapproprié.

Le type de méta-annotation @Target possède un unique membre nommé value. Ce membre est du type java.lang.annotation.ElementType[]. ElementType est un type énuméré dont les valeurs énumérées représentent des éléments de programme pouvant être annotés. Voici d'ailleurs ci-dessous la liste des valeurs énumérées :

type d'élément Applicable à
ANNOTATION_TYPE Déclaration de type d'annotation.
PACKAGE Paquetages.
TYPE Classes (en incluant enum) et interfaces (en incluant les types d'annotation).
METHOD Méthodes.
CONSTRUCTOR Constructeurs.
FIELD Les attributs (en incluant les constantes enum).
PARAMETER Paramètres de méthode ou de constructeur.
LOCAL_VARIABLE Variables locales.

Nous désirons par exemple que le type d'annotation @RapportErreur ne puisse être utilisable que pour les méthodes et les constructeurs. Voici ce que nous devons spécifer :

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public
@interface RapportErreur { String assignéA() default "[Personne]" ; int importance() = 0 ; }

 

Retention

La méta-annotation @Retention spécifie la durée pendant laquelle une annotation est retenue. Vous spécifierez au moins une des valeurs énoncées dans le tableau ci-dessous. Le type de méta-annotation @Retention possède donc un unique membre nommé value de type RententionPolicy. La valeur par défaut est RetentionPolicy.CLASS.

Règle de rétention Description
SOURCE Les annotations ne sont pas incluses dans le fichier de classe.
CLASS Les annotations sont incluses dans les fichiers de classe mais la machine virtuelle n'a pas besoin de les charger.
RUNTIME Les annotations sont incluses dans les fichiers de classe et chargées par la machine virtuelle.

 

Documented

La méta-annotation @Documented aide les outils de documentation comme Javadoc. Les annotations documentées doivent être traitées strictement de la même manière que les autres modificateurs tels que protected ou static dans le but de la documentation. L'utilisation d'autres annotations n'est pas incluse dans la documentation. @Documented est une annotation de marquage : elle ne possède pas de membre.

 

Inherited

La méta-annotation @Inherited est une annoation de marquage qui ne s'applique qu'aux annotaions de classes. Lorsqu'une classe possède une annotation héritée, toutes les sous-classes reçoivent automatiquement la même annotation. Cela facilite la création d'annotations qui se comportent de la même manière que les interfaces marqueur comme Serializable.

 

Choix du chapitre Annotation et réflexion

L'API de réflexion du paquetage java.lang.reflect a été étendue dans Java 5.0 afin de prendre en charge la lesture des annotations visibles à l'exécution (rappelez-vous qu'une annotation est uniquement visible à l'exécution si son type d'annotation est spécifié avec une rétentiotn d'exécution, c'est-à-dire si l'annotation est enregistrée dans le fichier de classe et si elle peut être lue par la machine virtuelle Java lors du chragement de la classe). Ce chapitre traite brièvement de ces nouvelles aptitudes réflexives.

L'interface java.lang.reflect.AnnotedElement représente un élément de programme dont les annotations peuvent être extraites. Cette interface est implémentée directement par java.lang.Package et java.lang.Class, et indirectement par les classes Method, Constructor et Field du paquetage java.lang.reflect. Les annotations des paramètres de méthode peuvent être obtenues à l'aide de la méthode getParameterAnnotations() de la classe Method ou Constructor.

Afin de tester la réflexion, nous reprenons le code de la classe Complexe auquel nous rajoutons un type d'annotation @Instable qui permet de spécifier si nous avons fini de décrire correctement une méthode ou pas. Nous devons ajouter l'annotation @Instable devant les méthodes dont le codage interne n'est pas tout à fait complet, comme c'est le cas ci-dessous avec les méthodes module() et argument().

import java.lang.annotation.*;
import java.lang.reflect.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Instable {}

class Complexe {
   private double réel;
   private double imaginaire;
   
   public Complexe(double r, double i) {
      réel = r;
      imaginaire = i;
   }

   @Override public String toString() {
      return "["+réel+", "+imaginaire+"]";
   }
   
   @Instable public double module() { return 0.0; }
   @Instable public double argument() { return 0.0; }
}

public class Main {
  public static void main(String[] args) throws NoSuchMethodException {
      Class classe = Complexe.class;
      Method méthode = classe.getMethod("module");
      boolean instable = méthode.isAnnotationPresent(Instable.class);
      ...
  }
}

Le code de la méthode principale utilise la méthode isAnnotationPresent() de AnnotatedElement afin de déterminer si une méthode est instable en interrogeant l'annotation @Instable. Notez que ce code utilise des littéraux de classe pour spécifier la classe et l'annotation à vérifier.

La méthode isAnnotationPresent() s'avère utile avec les annotation de marquage. Par contre, lorsque vous travaillez avec des annotations qui possèdent des membres, il s'avère plus intéressant de connaître la valeur de ces membres. Pour cela, nous utilisons la méthode getAnnotation().

C'est là qu'intervient la beauté du système d'annotations de Java ; si l'annotation spécifiée existe, l'objet retourné par cette méthode implémente l'interface du type d'annotation. Vous pouvez alors obtenir la valeur de n'importe quel membre de l'annotation en invoquant simplement la méthode du type d'annotation qui définit ce membre.

Afin de tester cette éventualité, je vous propose de reprendre la classe Complexe à laquelle je rajoute un nouveau type d'annotation @Auteur qui spécifie quel est le développeur qui écrit la partie de code concernée, ici les deux méthodes instables, module() et argument() :

import java.lang.annotation.*;
import java.lang.reflect.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Instable {}

@Retention(RetentionPolicy.RUNTIME)
@interface Auteur {
   String value();
}

class Complexe {
   private double réel;
   private double imaginaire;
   
   public Complexe(double r, double i) {
      réel = r;
      imaginaire = i;
   }

   @Override public String toString() {
      return "["+réel+", "+imaginaire+"]";
   }
   
   @Instable 
   @Auteur("Manu")
    public double module() { return 0.0; }
   
   @Instable 
   @Auteur("Emmanuel")
   public double argument() { return 0.0; }
}

public class Main {
  public static void main(String[] args) throws NoSuchMethodException {
      Class classe = Complexe.class;
      Method méthode = classe.getMethod("module");
      boolean instable = méthode.isAnnotationPresent(Instable.class);
      ...
      String auteur = méthode.getAnnotation(Auteur.class).value();
      ...
  }
}

Notez que ces méthodes réflexives résolvent correctement les valeurs d'annotation par défaut à votre place. Si une annotation n'inclut pas une valeur pour un membre possédant une valeur par défaut, cette dernière est obtenue depuis le type d'annotation lui-même.