Développons en Java
v 2.30   Copyright (C) 1999-2022 .   
10. Les énumérations (type enum) 12. Les expressions lambda Imprimer Index Index avec sommaire Télécharger le PDF

 

11. Les annotations

 

chapitre    1 1

 

Niveau : niveau 3 Intermédiaire 

 

Java SE 5 a introduit les annotations qui sont des métadonnées incluses dans le code source. Les annotations ont été spécifiées dans la JSR 175 : leur but est d'intégrer au langage Java des métadonnées.

Des métadonnées étaient déjà historiquement mises en oeuvre avec Java notamment avec Javadoc ou exploitées par des outils tiers notamment XDoclet : l'outil open source XDoclet propose depuis longtemps des fonctionnalités similaires aux annotations. Avant Java 5, seul l'outil Javadoc utilisait des métadonnées en standard pour générer une documentation automatique du code source.

Javadoc propose l'annotation @deprecated qui bien qu'utilisée dans les commentaires permet de marquer une méthode comme obsolète et de faire afficher un avertissement par le compilateur.

Le défaut de Javadoc est d'être trop spécifique à l'activité de génération de documentation même si le tag deprecated est aussi utilisé par le compilateur.

Depuis leur introduction dans Java 5, les annotations sont de plus en plus utilisées dans le développement d'applications avec les plates-formes Java SE et Java EE.

Ce chapitre contient plusieurs sections :

 

11.1. La présentation des annotations

Les annotations de Java 5 apportent une standardisation des métadonnées dans un but généraliste. Ces métadonnées associés aux entités Java peuvent être exploitées à la compilation ou à l'exécution.

Java a été modifié pour permettre la mise en oeuvre des annotations :

Les annotations peuvent être utilisées avec quasiment tous les types d'entités et de membres de Java : packages, classes, interfaces, constructeurs, méthodes, champs, paramètres, variables ou annotations elles-mêmes.

Java propose plusieurs annotations standard et permet la création de ses propres annotations.

Une annotation précède l'entité qu'elle concerne. Elle est désignée par un nom précédé du caractère @.

Il existe plusieurs catégories d'annotations :

Les arguments fournis en paramètres d'une annotation peuvent être de plusieurs types : les chaînes de caractères, les types primitifs, les énumérations, les annotations, le type Class.

Les annotations sont définies dans un type d'annotation. Une annotation est une instance d'un type d'annotation. Les paramètres d'une annotation peuvent avoir des valeurs par défaut.

La disponibilité d'une annotation est définie grâce à une retention policy.

Les usages des annotations sont nombreux : génération de documentations, de code, de fichiers, ORM (Object Relational Mapping), ...

Les annotations ne sont guère utiles sans un mécanisme permettant leur exploitation.

Une API est proposée pour assurer ces traitements : elle est regroupée dans les packages com.sun.mirror.apt, com.sun.mirror.declaration, com.sun.mirror.type et com.sun.mirror.util.

L'outil apt (annotation processing tool) permet un traitement des annotations personnalisées durant la phase de compilation (compile time). L'outil apt permet la génération de nouveaux fichiers mais ne permet pas de modifier le code existant.

Il est important de se souvenir que lors du traitement des annotations le code source est parcouru mais il n'est pas possible de modifier ce code.

L'API reflexion est enrichie pour permettre de traiter les annotations lors de la phase d'exécution (runtime).

Java 6 intègre deux JSR concernant les annotations :

L'Api Pluggable Annotation Processing permet d'intégrer le traitement des annotations dans le processus de compilation du compilateur Java ce qui évite d'avoir à utiliser apt.

Les annotations vont évoluer dans la plate-forme Java notamment au travers de plusieurs JSR qui sont en cours de définition :

 

11.2. La mise en oeuvre des annotations

Les annotations fournissent des informations sur des entités : elles n'ont pas d'effets directs sur les entités qu'elles concernent.

Les annotations utilisent leur propre syntaxe. Une annotation s'utilise avec le caractère @ suivi du nom de l'annotation : elle doit obligatoirement précéder l'entité qu'elle annote. Par convention, les annotations s'utilisent sur une ligne dédiée.

Les annotations peuvent s'utiliser sur les packages, les classes, les interfaces, les méthodes, les constructeurs et les paramètres de méthodes.

Exemple :
@Override
public void maMethode() {
}

Une annotation peut avoir un ou plusieurs attributs : ceux-ci sont précisés entre parenthèses, séparés par une virgule. Un attribut est de la forme clé=valeur.

Exemple :
@SuppressWarnings(value = "unchecked")
void maMethode() { }

Lorsque l'annotation ne possède qu'un seul attribut, il est possible d'omettre son nom.

Exemple :
@SuppressWarnings("unchecked")
void maMethode() { }

Un attribut peut être de type tableau : dans ce cas, les différentes valeurs sont fournies entre accolades, chaque valeur placée entre guillemets et séparée de la suivante par une virgule.

Exemple :
@SuppressWarnings(value={"unchecked", "deprecation"})

Le tableau peut contenir des annotations.

Exemple :
@TODOItems({
  @Todo(importance = Importance.MAJEUR, 
      description = "Ajouter le traitement des erreurs", 
      assigneA = "JMD", 
      dateAssignation = "07-11-2007"),
  @Todo(importance = Importance.MINEURE, 
      description = "Changer la couleur de fond", 
      assigneA = "JMD", 
      dateAssignation = "13-12-2007")
})

 

11.3. L'utilisation des annotations

Les annotations prennent une place de plus en plus importante dans la plate-forme Java et dans de nombreuses API open source.

Les utilisations des annotations concernent plusieurs fonctionnalités :

 

11.3.1. La documentation

Les annotations peuvent être mises en oeuvre pour permettre la génération de documentations indépendantes de JavaDoc : listes de choses à faire, de services ou de composants, ...

Il peut par exemple être pratique de rassembler certaines informations mises sous la forme de commentaires dans des annotations pour permettre leur traitement.

Par exemple, il est possible de définir une annotation qui va contenir les métadonnées relatives aux informations sur une classe. Traditionnellement, une classe débute par un commentaire d'en-tête qui contient des informations sur l'auteur, la date de création, les modifications, ... L'idée est de fournir ces informations dans une annotation dédiée. L'avantage est de facilement extraire et manipuler ces données qui ne seraient qu'informatives sous leur forme de commentaires.

 

11.3.2. L'utilisation par le compilateur

Les trois annotations fournies en standard avec la plate-forme entrent dans cette catégorie qui consiste à faire réaliser par le compilateur quelques contrôles basiques.

 

11.3.3. La génération de code

Les annotations sont particulièrement adaptées à la génération de code source afin de faciliter le travail des développeurs notamment sur des tâches répétitives.

Attention, le traitement des annotations ne peut pas modifier le code existant mais simplement créer de nouveaux fichiers sources.

 

11.3.4. La génération de fichiers

Les API standard ou les frameworks open source nécessitent fréquemment l'utilisation de fichiers de configuration ou de déploiement généralement au format XML.

Les annotations peuvent proposer une solution pour maintenir le contenu de ces fichiers par rapport aux entités incluses dans le code de l'application.

La version 5 de Java EE fait un important usage des annotations dans le but de simplifier les développements de certains composants notamment les EJB, les entités et les services web. Pour cela, l'utilisation de descripteurs est remplacée par l'utilisation d'annotations ce qui rend le code plus facile à développer et plus clair.

 

11.3.5. Les API qui utilisent les annotations

De nombreuses API standard utilisent les annotations depuis leur intégration dans Java notamment :

De nombreuses API open source utilisent aussi les annotations notamment JUnit, TestNG, Hibernate, ...

 

11.4. Les annotations standard

Java 5 propose plusieurs annotations standard.

 

11.4.1. L'annotation @Deprecated

Cette annotation a un rôle similaire au tag de même nom de Javadoc.

C'est un marqueur qui précise que l'entité concernée est obsolète et qu'il ne faudrait plus l'utiliser. Elle peut être utilisée avec une classe, une interface ou un membre (méthode ou champ)

Exemple :
public class TestDeprecated {

  public static void main(String[] args) {
    MaSousClasse td = new MaSousClasse();
    td.maMethode();
  }
}

@Deprecated
class MaSousClasse {

  /**
   * Afficher un message de test
   * @deprecated methode non compatible
   */
  @Deprecated 
  public void maMethode() {
    System.out.println("test");
  }  
}

Les entités marquées avec l'annotation @Deprecated devraient être documentées avec le tag @deprecated de Javadoc en lui fournissant la raison de l'obsolescence et éventuellement l'entité de substitution.

Il est important de tenir compte de la casse : @Deprecated pour l'annotation et @deprecated pour Javadoc.

Lors de la compilation, le compilateur donne une information si une entité obsolète est utilisée.

Exemple :
C:\Documents and Settings\jmd\workspace\Tests>javac TestDeprecated.java
Note: TestDeprecated.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

L'option -Xlint :deprecation permet d'afficher le détail sur les utilisations obsolètes.

Exemple :
C:\Documents and Settings\jmd\workspace\Tests>javac -Xlint:deprecation TestDepre
cated.java
TestDeprecated.java:7: warning: [deprecation] MaSousClasse in unnamed package ha
s been deprecated
    MaSousClasse td = new MaSousClasse();
    ^
TestDeprecated.java:7: warning: [deprecation] MaSousClasse in unnamed package ha
s been deprecated
    MaSousClasse td = new MaSousClasse();
                          ^
TestDeprecated.java:8: warning: [deprecation] maMethode() in MaSousClasse has be
en deprecated
    td.maMethode();
      ^
3 warnings

Il est aussi possible d'utiliser l'option -deprecation de l'outil javac.

 

11.4.2. L'annotation @Override

Cette annotation est un marqueur utilisé par le compilateur pour vérifier la réécriture de méthodes héritées.

@Override s'utilise pour annoter une méthode qui est une réécriture d'une méthode héritée. Le compilateur lève une erreur si aucune méthode héritée ne correspond.

Exemple :
@Override
public void maMethode() {
}

Son utilisation n'est pas obligatoire mais recommandée car elle permet de détecter certains problèmes.

Exemple :
public class MaClasseMere {
}

class MaClasse extends MaClasseMere {
  
  @Override
  public void maMethode() {    
  }
}

Ceci est particulièrement utile pour éviter des erreurs de saisie dans le nom des méthodes à redéfinir.

Exemple :
public class TestOverride { 

  private String nom;
  private long id;
  
  public int hasCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + (int) (id ^ (id >>> 32));
    result = PRIME * result + ((nom == null) ? 0 : nom.hashCode());
    return result;
  }
}

Dans l'exemple ci-dessous, le développeur souhaitait redéfinir la méthode hashCode() mais suite à une faute frappe a simplement défini une nouvelle méthode nommée hasCode(). Cette classe se compile parfaitement mais elle comporte une erreur qui est signalée en utilisant l'annotation @Override.

Exemple :
public class TestOverride { 

  private String nom;
  private long id;
  
  @Override
  public int hasCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + (int) (id ^ (id >>> 32));
    result = PRIME * result + ((nom == null) ? 0 : nom.hashCode());
    return result;
  }
}


Résultat :
C:\Documents and Settings\jmd\workspace\Tests>javac TestOverride.java
TestOverride.java:6: method does not override or implement a method from a super
type
  @Override
  ^
1 error

 

11.4.3. L'annotation @SuppressWarning

Les compilateurs peuvent détecter des cas qui sont potentiellement suspicieux et qui devraient nécessiter d'être regardés attentivement par les développeurs mais n'empêchent pas la compilation. Les compilateurs les reportent sous la forme d'avertissements (warning).

Les warnings indiqués par un compilateur permettent de préciser un risque potentiel dans le code : cela ne représente qu'un risque et pas une erreur. Cependant, il est risqué d'ignorer un warning sans s'interroger au préalable sur sa pertinence. Il est même préférable de corriger l'origine du warning si c'est possible plutôt que de l'ignorer.

Il arrive parfois que ces avertissements, qui sont des risques potentiels, ne soient pas fondés après vérification, mais soient des faux positifs ou qu'il n'y ait pas de moyen de les résoudre sans avoir un impact non souhaité sur les fonctionnalités. Si vous êtes sûr que le warning peut être ignoré sans risque alors il est possible d'utiliser l'annotation @SuppressWarnings pour demander au compilateur de l'ignorer.

Elle permet de demander au compilateur d'ignorer un ou plusieurs avertissements pouvant être émis lors de la compilation de l'élément annoté ou un de ses éléments enfants. L'ensemble des avertissements d'un élément se cumule avec ceux définis sur un élément enfant pour ce dernier. Exemple : si un avertissement est ignoré sur une classe et un autre sur une méthode, les deux avertissements seront ignorés pour la méthode.

L'annotation @SuppressWarnings est une annotation standard fournie dans le JDK depuis Java 5.

 

11.4.3.1. L'utilisation de l'annotation @SuppressWarnings

L'annotation @SuppressWarnings ne peut être utilisée que sur une déclaration. Elle peut donc s'utiliser sur différents éléments :

Exemple ( code Java 5.0 ) :
@SuppressWarnings("unchecked")
public class MaClasse{
  // ...
}

Exemple ( code Java 5.0 ) :
public class MaClasse {
 
  @SuppressWarnings("unchecked")
  public void maMathode() {
    // ...
  }
}

Exemple ( code Java 5.0 ) :
public class MaClasse {
 
  @SuppressWarnings({ "rawtypes", "unchecked" })
  private List liste = (List<String>) new ArrayList();
}

Attention : ignorer un avertissement peut cacher un bug potentiel. Il est donc préférable de tenter de le corriger plutôt que de l'ignorer. L'annotation @SuppressWarnings ne devrait être utilisée que lorsque cela est nécessaire et il faut toujours avoir une bonne connaissance des conséquences que son utilisation pourrait engendrer.

 

11.4.3.2. Les attributs de l'annotation @SuppressWarnings

L'attribut de l'annotation @SuppressWarnings permet d'indiquer un ou plusieurs avertissements qui doivent être ignorés par le compilateur pour l'élément annoté. Il est possible de mettre des doublons mais dans ce cas seul le premier sera pris en compte.

L'annotation @SuppressWarnings ne possède qu'un seul attribut qui est un tableau de String.

Il est possible de préciser une seule valeur :

Exemple ( code Java 5.0 ) :
@SuppressWarnings("unchecked")

Il est aussi possible de préciser plusieurs valeurs en les séparant avec une virgule :

Exemple ( code Java 5.0 ) :
@SuppressWarnings({"unchecked", "deprecation"})

L'annotation est standard, par contre les valeurs à lui passer en paramètre sont dépendantes de chaque compilateur. Ainsi les warnings précisés comme attribut de l'annotation sont spécifiques à chaque compilateur car chacun n'a pas les mêmes capacités de détections des avertissements.

Seules trois valeurs sont définies dans la JLS (Java Langage Specification) :

Toutes les autres valeurs sont spécifiques à chaque compilateur ou IDE utilisé.

Les avertissements inconnus du compilateur sont ignorés mais il peut émettre un avertissement relatif au fait qu'il ne connait pas l'avertissement indiqué.

 

11.4.3.3. Les options du compilateur pour afficher les warnings

Par défaut, le compilateur javac n'affiche pas les warnings : certains d'entre eux affichent une note pour indiquer leur présence.

Résultat :
C:\java>javac MaClasse.java
Note: MaClasse.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

L'option -Xlint permet de gérer les warnings qui doivent être affichés par le compilateur.

Comme indiqué, pour avoir le détail, il faut relancer javac avec l'option -Xlint suivi du caractère deux points et d'un type de warning. Les valeurs possibles pour le type de warning dépendent pour la plupart du compilateur utilisé.

Résultat :
C:\java>javac -Xlint:unchecked MaClasse.java

Il est possible de préciser plusieurs types en les séparant par une virgule.

Résultat :
C:\java>javac -Xlint:rawtype,unchecked MaClasse.java

Il est aussi possible de préfixer un type de warning avec un caractère moins pour le désactiver.

Résultat :
C:\java>javac -Xlint:all,-serial MaClasse.java

Enfin, il est possible d'activer tous les warnings en utilisant simplement l'option -Xlint ou -Xlint:all

Résultat :
C:\java>javac -Xlint:all MaClasse.java
C:\java>javac -Xlint MaClasse.java

Pour obtenir la liste des attributs utilisable dans @SuppressWarnings par un compilateur javac du JDK utilisé, il suffit d'utiliser son option -X

Résultat :
C:\java>javac -X

Parmi les options affichées, il y a celles relatives aux avertissements précisés dans l'option -Xlint.

A partir de Java 17, il faut utiliser l'option --help-lint de javac pour obtenir la liste des avertissements supporté par -Xlint.

Résultat :
C:\java>javac --help-lint

Pour désactiver tous les avertissements du compilateur, il est possible d'utiliser l'option -Xlint:none ou l'option -nowarn.

Il est aussi possible de désactiver un avertissement particulier en le préfixant avec un moins dans l'option -Xlint. Exemple : -Xlint:serial

Jusqu'à la version 8 des spécifications du langage Java, seules les avertissements unchecked et deprecation devraient être implémentées par tous les compilateurs.

D'autres IDE, compilateurs ou des outils d'analyse de code peuvent supporter d'autres valeurs possibles pour @SuppressWarnings : ces valeurs sont alors donc spécifiques à ces outils.

 

11.4.3.4. Les options du compilateur d'OpenJDK pour afficher les warnings

Le compilateur javac d'OpenJDK propose plusieurs avertissements :

Valeur

Rôle

Version
de Java

all

Activer tous les avertissements

5

auxiliaryclass

Avertissement lors de l'utilisation d'une classe auxiliaire utilisée dans une classe définie dans un autre fichier

8

cast

Avertissement lors de l'utilisation de cast inutile

6

classfile

 

7

deprecation

Avertissement lors de l'utilisation d'éléments deprecated. Similaire à l'option historique -deprecation du compilateur

5

dep-ann

Avertissement concernant des éléments marqués comme deprecated dans la Javadoc sans qu'elle soit annotée avec @Deprecated

7

divzero

Avertissement concernant une division avec la constante entière 0

6

empty

Avertissement concernant une instruction if sans traitement

6

exports

 

9

fallthrough

Avertissement lors de l'absence possible d'une instruction break dans une clause case d'un switch

5

finally

Avertissement concernant l'utilisation d'une instruction return dans un bloc finally

5

missing-explicit-ctor Avertissement sur les constructeurs explicites manquants dans les classes publiques et protégées dans les paquets exportés 15

module

Avertissement concernant les noms de module qui ne devraient pas se terminer par des chiffres

9

opens

 

9

options

Avertissement concernant l'utilisation de certaines combinaisons d'options du compilateur

7

overloads

Avertissement concernant la définition de surcharges d'une méthode qui pourraient engendrer des ambigu�tés pour déterminer celle à invoquer

9

overrides

Avertissement concernant la redéfinition d'une méthode avec un tableau en un varargs ou vice versa

6

path

Avertissement concernant un élément du classpath inexistant

5

preview Avertissement concernant l'utilisation de fonctionnalités en mode preview 11

processing

Avertissement concernant les annotations qui ne sont pas traitées par un processeur d'annotations

7

rawtypes

Avertissement concernant l'utilisation d'une classe générique sans préciser le type générique

7

removal

Avertissement concernant l'utilisation d'un élément de l'API est marqué comme étant dépréciée avec l'attribut forRemoval=true

9

requires-automatic

Avertissement concernant l'utilisation d'un automatic module avec une clause requires

9

requires-transitive-automatic

Avertissement concernant l'utilisation d'un automatic module avec une clause requires transitive

9

serial

Avertissement concernant l'absence d'un champ serialVersionUID dans une classe Serializable

5

static

Avertissement concernant l'accès à un membre static à partir d'une instance

7

strictfp Avertissement sur l'utilisation inutile du modificateur strictfp 17
text-blocks Avertissement sur les caractères d'espace blanc incohérents dans l'indentation des blocs de texte 13

try

Avertissement concernant une utilisation d'une instruction try-with-resources

7

unchecked

Avertissement concernant le fait que le compilateur ne peut pas garantir la vérification de type (type safety)

5

varargs

Avertissement concernant une possible utilisation d'un varargs en paramètre d'une méthode

7

none

Désactiver tous les avertissements

6

 

11.4.3.4.1. auxiliaryclass

Le compilateur émet un avertissement lorsqu'une classe auxiliaire est utilisée dans une classe qui n'est pas définie dans le même fichier source. Une classe auxiliaire est une classe définie dans un fichier dont le nom n'est pas celui de la classe elle-même.

Exemple :
public class MaClasse {
}

class MaClasseAuxiliaire {
}

Exemple :
public class MonAutreClasse {
 
  MaClasseAuxiliaire mac = null;
}

C'est légal en Java mais il n'est pas recommandé d'utiliser une classe auxiliaire en dehors du fichier dans lequel elle est définie.

Résultat :
C:\java>javac -Xlint MaClasse.java MonAutreClasse.java
MonAutreClasse.java:3: warning: auxiliary class MaClasseAuxiliaire in
MaClasse.java should not be accessed from outside its own source file
  MaClasseAuxiliaire mac = null;
  ^
1 warning

Une solution pour éviter cette situation est de définir la classe non pas comme une classe auxiliaire mais comme une classe imbriquée.

Exemple :
public class MaClasse {
 
  static class MaClasseAuxiliaire {
  }
}

Exemple :
public class MonAutreClasse {
 
  MaClasse.MaClasseAuxiliaire mca = null;
}

Résultat :
C:\java>javac -Xlint MaClasse.java MonAutreClasse.java
C:\java>

 

11.4.3.4.2. cast

L'utilisation inutile d'un cast provoque un avertissement de la part du compilateur.

Exemple :
public class MaClasse {

  public static void main(String... args) {
    String message = (String) "texte";
  }
}

Résultat :
C:\java>javac -Xlint:all MaClasse.java
MaClasse.java:4: warning: [cast] redundant cast to String
    String message = (String) "texte";
                     ^
1 warning

Il est possible de désactiver l'avertissement du compilateur relatif à l'utilisation inutile d'un cast.

Exemple :
public class MaClasse {
 
  @SuppressWarnings("cast")
  public static void main(String... args) {
    String message = (String) "texte";
  }
}

Résultat :
C:\java>javac -Xlint:cast MaClasse.java
 
C:\java>

Plutôt que d'ignorer cet avertissement, il est de préférable de le corriger simplement en supprimant le cast concerné.

 

11.4.3.4.3. deprecation

L'utilisation d'un élément deprecated provoque un avertissement de la part du compilateur.

Exemple :
import java.util.Date;
 
public class MaClasse {
 
  public static void main(String... args) {
    Date date =  new Date(10,10,10);
  }
}

Résultat :
C:\java> javac MaClasse.java
Note: MaClasse.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

C:\java> javac -Xlint:deprecation MaClasse.java
MaClasse.java:6: warning: [deprecation] Date(int,int,int) in Date has been deprecated
          Date date =  new Date(10,10,10);
                       ^
1 warning

Il est possible de désactiver l'avertissement du compilateur relatif à l'utilisation d'un élément deprecated.

Exemple :
import java.util.Date;
 
public class MaClasse {
 
  public static void main(String... args) {
    @SuppressWarnings("deprecation")
    Date date =  new Date(10,10,10);
  }
}

Résultat :
C:\java> javac MaClasse.java

C:\java>

Jusqu'à Java 8, pour demander au compilateur d'OpenJDK d'ignorer les avertissements relatifs à l'utilisation d'élément deprecated, il fallait utiliser l'annotation @SuppressWarnings("deprecation").

A partir de Java 9, la valeur « deprecation » ne concerne que les éléments deprecated forRemoval=false. Les éléments deprecated forRemoval=true génère un avertissement de la part du compilateur. Ceci permet de maintenir l'avertissement pour ces éléments et ainsi éviter de ne pas être informé de leur futur suppression. C'est nécessaire car de nombreux développeurs supposent que les éléments deprecated ne seront jamais retirés pour des raisons de compatibilité ascendante.

Pour demander au compilateur d'OpenJDK d'ignorer les avertissements pour ces éléments, il faut utiliser l'annotation @SuppressWarnings("removal"). Pour demander d'ignorer l'utilisation de tous les types d'éléments deprecated, il faut utiliser l'annotation @SuppressWarnings({"deprecation", "removal"}).

 

11.4.3.4.4. dep-ann

La définition d'une méthode dont la Javadoc précise qu'elle est deprecated sans qu'elle soit annotée avec @Deprecated provoque un avertissement de la part du compilateur.

Exemple :
public class MaClasse {

  public static void main(String... args) {
    methodeDeprecated();
  }
  
  /**
   * @deprecated
   */
  public static void methodeDeprecated() { 
  }
}

Résultat :
C:\java> javac -Xlint:all MaClasse.java
MaClasse.java:12: warning: [dep-ann] deprecated item is not annotated with
@Deprecated
  public static void methodeDeprecated() {
                     ^
1 warning

Il est possible de désactiver cet avertissement.

Exemple :
public class MaClasse {
 
  public static void main(String... args) {
    methodeDeprecated();
  }
  
  /**
   * @deprecated
   */
  @SuppressWarnings("dep-ann")
  public static void methodeDeprecated() { 
  }
}

Résultat :
C:\java> javac -Xlint:dep-ann MaClasse.java
 
C:\java>

 

11.4.3.4.5. divzero

Une division avec la valeur littérale zéro provoque un avertissement de la part du compilateur.

Exemple :
public class MaClasse {

  public static void main(String... args) {
    long valeur = 123 / 0;
  }
}

Résultat :
C:\java>javac -Xlint:divzero MaClasse.java
MaClasse.java:6: warning: [divzero] division by zero
    long valeur = 123 / 0;
                        ^
1 warning

Il est possible de désactiver l'avertissement du compilateur relatif à division par zéro.

Exemple :
public class MaClasse {
 
  public static void main(String... args) {
    @SuppressWarnings("divzero")
    long valeur = 123 / 0;
  }
}

Résultat :
C:\java> javac -Xlint:divzero MaClasse.java
 
C:\java>

Attention : cet avertissement ne fonctionne que lors de l'utilisation de la valeur littérale zéro ou d'une constante dont la valeur est zéro.

Exemple :
public class MaClasse {
 
  private static final int ZERO = 0;
 
  public static void main(String... args) {
    int zero = 0;
    long valeur = 123 / zero;

    valeur = 123 / ZERO;
  }
}

Résultat :
C:\java>javac -Xlint:divzero MaClasse.java
MaClasse.java:11: warning: [divzero] division by zero
        valeur = 123 / ZERO;
                       ^
1 warning

Ignorer cet avertissement ne va pas empêcher la levée d'une exception de type java.lang.ArithmeticException à l'exécution du code.

Résultat :
C:\java>java MaClasse
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at MaClasse.main(MaClasse.java:9)

 

11.4.3.4.6. empty

L'utilisation d'un bloc de code vide dans une instruction if provoque un avertissement de la part du compilateur.

Exemple :
public class MaClasse {

  public static void main(String... args) {
    if (args.length > 0);
    System.out.println("Presence de parametres");
  }
}

Résultat :
C:\java>javac -Xlint:empty MaClasse.java
MaClasse.java:5: warning: [empty] empty statement after if
    if (args.length > 0);
                        ^
1 warning

Il est possible de désactiver l'avertissement du compilateur relatif à l'utilisation d'un bloc de code vide dans une instruction if.

Exemple :
public class MaClasse {
 
  @SuppressWarnings("empty")
  public static void main(String... args) {
    if (args.length > 0);
    System.out.println("Presence de parametres");
  }
}

Résultat :
C:\java>javac -Xlint:empty MaClasse.java
 
C:\java>

Attention : cet avertissement ne concerne que les blocs vides pour les instructions if sans clause else.

Exemple :
public class MaClasse {
 
  public static void main(String... args) {
    if (args.length > 0);
    else;

    for(String arg : args);
    
    while (args.length == 0);
    do;
    
    while (args.length == 0);
  }
}

Résultat :
C:\java>javac -Xlint:all MaClasse.java
 
C:\java>

Dans l'exemple ci-dessus, aucun avertissement n'est affiché par le compilateur.

 

11.4.3.4.7. fallthrough

Par défaut sans instruction break à la fin d'une instruction case, le code d'une instruction case exécute le code des instructions case suivantes jusqu'à la rencontre d'une instruction break.

Parfois bien pratique notamment si le même code concerne plusieurs valeurs, cela peut aussi parfois amener à des bugs subtils lors de l'oubli d'instructions breaks. C'est la raison pour laquelle si une instruction case possède au moins une instruction et pas d'instruction break alors le compilateur génère un avertissement.

Exemple :
public class MaClasse {

  public static void main(String... args) {
    String valeur = "A";
    switch(valeur) {
    case "A":
      System.out.println("A");
    case "B":
      System.out.println("B");
    }
  }
}

Résultat :
C:\java> javac -Xlint:all MaClasse.java
MaClasse.java:9: warning: [fallthrough] possible fall-through into case
    case "B":
   
^
1 warning

Il est possible de désactiver cet avertissement du compilateur.

Exemple :
public class MaClasse {
 
  @SuppressWarnings("fallthrough")
  public static void main(String... args) {
    String valeur = "A";
    switch(valeur) {
    case "A":
      System.out.println("A");
    case "B":
      System.out.println("B");
    }
  }
}

Résultat :
C:\java>javac -Xlint:fallthrough MaClasse.java
 
C:\java>

 

11.4.3.4.8. finally

L'utilisation d'une instruction return dans un bloc finally n'est pas une bonne pratique et sa bonne exécution n'est pas garantie : elle provoque donc un avertissement de la part du compilateur.

Exemple :
public class MaClasse {
 
  public static int convertir(String valeur) {
    int resultat = -1;
    try {
      resultat = Integer.parseInt(valeur);
    } catch (ArithmeticException ae) {
    } finally {
      return resultat;
    }
  }
}

Résultat :
C:\java>javac -Xlint:finally MaClasse.java
MaClasse.java:18: warning: [finally] finally clause cannot complete normally
    }
   
^
1 warning

Il est possible de désactiver l'avertissement du compilateur relatif à l'utilisation d'une instruction return dans un bloc finally.

Exemple :
public class MaClasse {
 
  @SuppressWarnings("finally")
  public static int convertir(String valeur) {
    int resultat = -1;
    try {
      resultat = Integer.parseInt(valeur);
    } catch (ArithmeticException ae) {
    } finally {
      return resultat;
    }
  }
}

Résultat :
C:\java> javac -Xlint:finally MaClasse.java
 
C:\java>

D'autres situations utilisant un return peuvent générer cet avertissement.

Exemple :
public class MaClasse {
 
  public static void main(String... args) { 
    System.out.println(saluer("Pierre"));
  }
   
  public static String saluer(String nom) {
    String message = "Bonjour ";
 
    try {
      message+=nom;
    } finally {
      return message.toUpperCase();
    }
  }
}

Exemple :
public class MaClasse {
 
  public static void main(String... args) { 
    System.out.println(convertir("ABC"));
  }
   
  public static int convertir(String valeur) {
    int resultat = 0;
    try {
      resultat = Integer.parseInt(valeur);
    } catch (ArithmeticException ae) {
      return -1;
    } finally {
      return resultat;
    }
  }
}

Tous les exemples ci-dessus sont syntaxiquement valides en Java mais ne sont pas de bonnes pratiques : il est donc préférable de ne pas ignorer cet avertissement.

 

11.4.3.4.9. module

Le compilateur émet un avertissement lorsqu'un des éléments qui compose le nom d'un module se termine par un nombre car cela devrait être évité.

Exemple ( code Java 9 ) :
module fr.jmdoudoux.dej2.module123 {
}

Résultat :
C:\java>javac module-info.java
module-info.java:1: warning: [module] module name component module123
should avoid terminal digits
module fr.jmdoudoux.dej2.module123 {
                         ^
module-info.java:1: warning: [module] module name component dej2 should
avoid terminal digits
module fr.jmdoudoux.dej2.module123 {
                    ^
2 warnings

 

11.4.3.4.10. options

Le compilateur émet un avertissement lorsque certaines combinaisons d'options sont utilisées lors de l'invocation du compilateur.

Résultat :
C:\java>javac -source 1.7 MaClasse.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7

 

11.4.3.4.11. overloads

Le compilateur émet un avertissement lorsque des surcharges d'une méthode peuvent préter à confusion.

Exemple ( code Java 8 ) :
import java.util.function.Consumer;
      
public class MaClasse {
   
  static void traiter(int  i, Consumer<Integer> action) { }
   
  static void traiter(long l, Consumer<Long>    action) { }   
}

Résultat :
C:\java>javac -Xlint:all MaClasse.java
MaClasse.java:5: warning: [overloads]
traiter(int,Consumer<Integer>) in MaClasse is potentially ambiguous with
traiter(long,Consumer<Long>) in MaClasse
    static void traiter(int  i, Consumer<Integer> action) { }
                ^
1 warning

Dans cet exemple, le compilateur nous avertit sur un potentiel problème qui pourrait empêcher la détermination claire de la surcharge à invoquer.

Exemple ( code Java 8 ) :
import java.util.function.Consumer;
      
public class MaClasse {
   
  static void traiter(int  i, Consumer<Integer> action) { }
   
  static void traiter(long l, Consumer<Long>    action) { }
   
  public static void main(String[] args) {
    traiter(1, (i) -> { });
  }
}

Résultat :
C:\java>javac -Xlint:all MaClasse.java
MaClasse.java:5: warning: [overloads] traiter(int,Consumer<Integer>)
in MaClasse is potentially ambiguous with traiter(long,Consumer<Long>) in MaClasse
    static void traiter(int  i, Consumer<Integer> action) { }
                ^
MaClasse.java:9: error: reference to traiter is ambiguous
      traiter(1, (i) -> { });
      ^
  both method
traiter(int,Consumer<Integer>) in MaClasse and method
traiter(long,Consumer<Long>) in MaClasse match
1 error
1 warning

Pour résoudre cette erreur, il faut indiquer le type explicitement pour permettre de déterminer de manière univoque la surcharge à invoquer. Dans cet exemple, il est par exemple possible de préciser explicitement le type générique du paramètre de type Consumer.

Exemple ( code Java 8 ) :
import java.util.function.Consumer;

public class MaClasse {
   
  static void traiter(int  i, Consumer<Integer> action) { }
   
  static void traiter(long l, Consumer<Long>    action) { }
   
  public static void main(String[] args) {
    traiter(1, (Long i) -> { });
  }
}

Résultat :
C:\java>javac -Xlint:all MaClasse.java
MaClasse.java:5: warning: [overloads] traiter(int,Consumer<Integer>)
in MaClasse is potentially ambiguous with traiter(long,Consumer<Long>) in MaClasse
    static void traiter(int  i, Consumer<Integer> action) { }
                ^
1 warning

Il est possible d'utiliser l'annotation @SuppressWarning("overloads") pour demander au compilateur d'ignorer l'avertissement sur l'élément marqué.

Exemple ( code Java 8 ) :
import java.util.function.Consumer;

public class MaClasse {
   
  @SuppressWarnings("overloads")
  static void traiter(int  i, Consumer<Integer> action) { }
    
  @SuppressWarnings("overloads")
  static void traiter(long l, Consumer<Long>    action) { }

  public static void main(String[] args) {
    traiter(1, (Long i) -> { }); 
  }    
}

Résultat :
C:\java>javac -Xlint:all MaClasse.java
C:\java>

 

11.4.3.4.12. overrides

Le compilateur transforme un varargs un tableau dans le byte code.

L'avertissement overrides émis par le compilateur lors de la redéfinition d'une méthode dont le dernier paramètre est un tableau dans la classe mère et un varargs dans la classe fille.

Exemple :
import java.util.ArrayList;
import java.util.List;

public class MaClasse {

  protected List<String> elements = new ArrayList<>();

  public void ajouter(final String[] elmts) {
    for (final String element : elmts) {
      elements.add(element);
    }
  }
}

class MaClasseFille extends MaClasse {

  @Override
  public void ajouter(final String... elmts) {
    for (final String element : elmts) {
      elements.add(element);
    }
  }
}

Résultat :
C:\java> javac -Xlint:overrides MaClasse.java
MaClasse.java:19: warning: ajouter(String...) in MaClasseFille overrides
ajouter(String[]) in MaClasse; overridden method has no '...'
  public void ajouter(final String... elmts) {
              ^
1 warning

Cet avertissement survient aussi lors de la redéfinition d'une méthode dont le dernier paramètre est un varargs dans la classe mère et un tableau dans la classe fille.

Exemple :
import java.util.ArrayList;
import java.util.List;
 
public class MaClasse {
 
  protected List<String> elements = new ArrayList<>();
 
  public void ajouter(final String... elmts) {
    for (final String element : elmts) {
      elements.add(element);
    }
  }
}
 
class MaClasseFille extends MaClasse {
 
  @Override
  public void ajouter(final String[] elmts) {
    for (final String element : elmts) {
      elements.add(element);
    }
  }
}

Il est possible de désactiver cet avertissement du compilateur.

Exemple :
class MaClasseFille extends MaClasse {
 
  @Override
  @SuppressWarnings("overrides")
  public void ajouter(final String... elmts) {
    for (final String element : elmts) {
      elements.add(element);
    }
  }
}

Résultat :
C:\java> javac -Xlint:overrides MaClasse.java
 
C:\java>

L'option -Xlint:overrides ne remplace pas l'annotation @Override : d'ailleurs la première est un avertissement la seconde est une erreur de compilation.

L'option -Xlint:overrides concerne des situations de redéfinition de méthodes utilisant un tableau est un varargs.

 

11.4.3.4.13. path

Les classes sont chargées à partir du classpath qui peut être composé de répertoires ou de fichiers .jar. Il est très facile de faire une erreur typographique lors de la saisie des éléments du classpath notamment lorsque ce dernier contient de nombreux éléments.

Par défaut, le compilateur ne fournit aucun avertissement relatif aux éléments du classpath qui n'existent pas.

L'option -Xlint:path permet de demander au compilateur d'afficher sous la forme de warnings les éléments du classpath qui ne sont pas trouvés. Les warnings émis ne concernent donc pas l'analyse du code source mais elle est particulièrement utile pour identifier certains problèmes de chargement de classes.

Résultat :
C:\java> javac -cp .;./repinexistant;./inexistant.jar -Xlint:path MaClasse.java
warning: [path] bad path element ".\repinexistant": no such file or directory
warning: [path] bad path element ".\inexistant.jar": no such file or directory
2 warnings

 

11.4.3.4.14. preview

Le compilateur émet un avertissement lors de l'utilisation de fonctionnalités proposées en mode preview.

Exemple ( code Java 13 ) :
public class MaClasse {
  public static void main(String... args) {
       
    String message = """
                          Bonjour"""; 
 }
}

Résultat :
C:\java>javac --enable-preview -source 13 -Xlint:preview MaClasse.java
MaClasse.java:5: warning: [preview] text blocks are a preview feature and
may be removed in a future release.
    String message = """
                     ^
1 warning

Il n'est pas possible de demander d'ignorer cet avertissement ni avec l'annotation @SuppressWarnings ni avec le paramètre -Xlint. Le compilateur ne va pas tenir compte de cette demande.

Exemple ( code Java 13 ) :
public class MaClasse {
 
  public static void main(String... args) {
 
    @SuppressWarnings("preview")        
    String message = """
                          Bonjour""";
  }
}

Résultat :
C:\java>javac --enable-preview -source 13 MaClasse.java
Note: MaClasse.java uses preview language features.
Note: Recompile with -Xlint:preview
for details.
C:\java>javac --enable-preview -source 13 -Xlint:-preview MaClasse.java
Note: MaClasse.java uses preview language features.
Note: Recompile with -Xlint:preview
for details.

 

11.4.3.4.15. processing

Le compilateur peut émettre un avertissement pour les annotations qui ne sont pas traitées par un processeur d'annotations.

Par exemple, les deux annotations suivantes :

Exemple :
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(TYPE)
public @interface MonAnnotation {
}

Exemple :
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(TYPE)
public @interface MonAutreAnnotation {
}

Et une classe qui est processeur pour traiter l'annotation @MonAnnotation

Exemple :
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

@SupportedAnnotationTypes("MonAnnotation")
public class MonProcessor extends AbstractProcessor {

  @Override
  public boolean process(Set<? extends TypeElement> elems, RoundEnvironment renv) {
    Set<? extends Element> elements = renv.getElementsAnnotatedWith(MonAnnotation.class);
    for (Element e : elements) {
      processingEnv.getMessager().printMessage(Kind.NOTE, 
          "Utilisation de l'annotation @MonAnnotation sur la classe " + e.getSimpleName());
    }
    return true;
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.RELEASE_8;
  }
}

Résultat :
C:\java>javac MonProcessor.java

Il est possible d'utiliser le processeur avec le compilateur pour compiler une classe qui utilise l'annotation @MonAnntotation.

Exemple :
import java.util.ArrayList;
import java.util.List;

@MonAnnotation
public class MaClasse {
}

Résultat :
C:\java>javac -cp . -processor MonProcessor -proc:only MaClasse.java
Note: Utilisation de l'annotation
@MonAnnotation sur la classe MaClasse

Le compilateur peut lever un avertissement pour les annotations qui ne sont pas traités par un processeur en utilisant l'option -Xlint:processing.

Exemple :
import java.util.ArrayList;
import java.util.List;

@MonAutreAnnotation
public class MaClasse {
}

Résultat :
C:\java>javac -cp . -Xlint:processing -processor MonProcessor -proc:only MaClasse.java
warning: No processor claimed any of these annotations: MonAutreAnnotation
1 warning

 

11.4.3.4.16. rawtypes

L'utilisation d'une classe typée avec generic sans préciser le type souhaité provoque un avertissement de la part du compilateur.

Exemple :
import java.util.List;
import java.util.ArrayList;

public class MaClasse {
  public static void main(String... args) {
    List liste = new ArrayList();
  }
}

Résultat :
C:\java>javac -Xlint:rawtypes MaClasse.java
MaClasse.java:8: warning: [rawtypes] found raw type: List
    List liste = new ArrayList();
    ^
  missing type arguments for generic class List<E>
  where E is a type-variable:
    E extends Object declared in interface List
MaClasse.java:8: warning: [rawtypes] found raw type: ArrayList
    List liste = new ArrayList();
                     ^
  missing type arguments for generic class ArrayList<E>
  where E is a type-variable:
    E extends Object declared in class ArrayList
2 warnings

Il est possible de désactiver l'avertissement du compilateur relatif à l'absence du type generic.

Exemple :
import java.util.List;
import java.util.ArrayList;
 
public class MaClasse {
 
  public static void main(String... args) {
    @SuppressWarnings("rawtypes")
    List liste = new ArrayList();
  }
}

Résultat :
C:\java>javac -Xlint:rawtypes MaClasse.java
 
C:\java>

A défaut de mieux, il est possible de préciser le type generic <?> qui permet d'indiquer n'importe quelle type. L'idéal est d'indiquer un type précis dans le generic.

Exemple :
import java.util.ArrayList;
import java.util.List;
 
public class MaClasse {
 
  public static void main(String... args) {
    List<?> liste = new ArrayList<>();
  }
}

Résultat :
C:\java>javac -Xlint:all MaClasse.java
 
C:\java>

 

11.4.3.4.17. serial

La définition d'une classe implémentant l'interface Serialisable sans définir un attribut serialVersionUID provoque un avertissement de la part du compilateur.

Exemple :
import java.io.Serializable;
      
public class MaClasse implements Serializable {
}

Résultat :
C:\java>javac -Xlint:serial MaClasse.java
MaClasse.java:3: warning: [serial] serializable class MaClasse has no
definition of serialVersionUID
public class MaClasse implements Serializable {
       ^
1 warning

Il est possible de désactiver l'avertissement du compilateur relatif à l'absence d'un attribut serialVersionUUID d'une classe Serializable.

Exemple :
import java.io.Serializable;
      
@SuppressWarnings("serial")
public class MaClasse implements Serializable {
}

Résultat :
C:\java> javac -Xlint:serial MaClasse.java
 
C:\java> 

Il est fortement recommandé de définir explicitement un champ serialVersionUID plutôt que de la valeur générée. Cette génération dépend des implémentations et peut donc engendrer la levée d'exceptions de type InvalidClassException pendant les opérations de désérialisation. Ignorer les avertissements de type serial n'est donc pas recommandé.

 

11.4.3.4.18. static

L'invocation d'une méthode statique sur une instance provoque un avertissement de la part du compilateur.

Exemple :
public class MaClasse {

  public void methode() {
    this.methodeStatique();     
  }

  public static void methodeStatique() { 
  }
}

Résultat :
C:\java>javac -Xlint:all MaClasse.java
MaClasse.java:4: warning: [static] static method should be qualified by
type name, MaClasse, instead of by an expression
    this.methodeStatique();
        ^
1 warning

Il est possible de désactiver l'avertissement du compilateur relatif à l'invocation d'une m�thode statique sur une instance.

Exemple :
public class MaClasse {
 
  @SuppressWarnings("static")
  public void methode() {
    this.methodeStatique();      
  }
 
  public static void methodeStatique() { 
  }
}

Résultat :
C:\java>javac -Xlint:static MaClasse.java
 
C:\java>

Plutôt que d'ignorer cet avertissement, il est de préférable de le corriger simplement en remplaçant l'instance par la classe.

 

11.4.3.4.19. try

Une mauvaise utilisation d'une instruction try provoque un avertissement de la part du compilateur. C'est par exemple le cas, si la ressource n'est pas utilisée dans le bloc de code de l'instruction try.

Exemple (Java 7) :
import java.io.FileReader;
import java.io.IOException;

public class MaClasse {
  public static void main(String... args) {
    try (FileReader reader = new FileReader("monfichier.txt")) {
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }
  }
}

Résultat :
C:\java> javac -Xlint:all MaClasse.java
MaClasse.java:7: warning: [try] auto-closeable resource reader is never
referenced in body of corresponding try statement
    try (FileReader reader = new FileReader("monfichier.txt")) {
                    ^
1 warning

Il est possible de désactiver l'avertissement du compilateur relatif à une mauvaise utilisation d'une instruction try.

Exemple :
import java.io.FileReader;
import java.io.IOException;
 
public class MaClasse {
 
  @SuppressWarnings("try")
  public static void main(String... args) {
    try (FileReader reader = new FileReader("monfichier.txt")) {
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }
  }
}

Résultat :
C:\java>javac -Xlint:try MaClasse.java
 
C:\java>

 

11.4.3.4.20. unchecked

Les avertissements de type unchecked indique que le compilateur ne peut pas garantir la vérification de type lors de l'utilisation d'un type possédant un type generic sans préciser le type à utiliser. La sécurité de type (type-safety) signifie qu'un programme est considéré comme sûr s'il compile sans erreurs ni avertissements et ne génère pas d'exception inattendue de type ClassCastException lors de son exécution.

Lors de l'utilisation de génériques, il est fréquent d'avoir des avertissements de type unchecked de différentes origines :

Une bonne connaissance de l'utilisation des génériques permet de réduire les avertissements.

Exemple :
import java.util.List;
import java.util.ArrayList;

public class MaClasse {
  public static void main(String... args) {
    List liste = new ArrayList();
    liste.add("element1");
  }
}

Résultat :
C:\java> javac -Xlint:unchecked MaClasse.java
MaClasse.java:9: warning: [unchecked] unchecked call to add(E) as a member
of the raw type List

liste.add("element1");
                 ^
  where E is a type-variable:
    E extends Object declared in interface List
1 warning

Les avertissements de type unchecked sont fréquents dans un code legacy écrit avant Java 5 mixé avec du code utilisant des generics.

Exemple :
import java.util.List;
import java.util.ArrayList;
 
public class MaClasse {
 
  public static void main(String... args) {
    List liste = new ArrayList<String>();
    List<Integer> entiers = liste;
  }
}

Résultat :
C:\java>javac -Xlint:unchecked MaClasse.java
MaClasse.java:9: warning: [unchecked] unchecked conversion
        List<Integer> entiers = liste;
                                ^
  required: List<Integer>
  found:    List
1 warning

Ce genre d'affectation est autorisée par le compilateur à cause du type erasure qui permet d'assurer la compatibilité avec du code avant les génériques. Mais un avertissement prévient le développeur d'une possible erreur de type ClassCastException à l'exécution.

Le compilateur émet aussi un avertissement de type unchecked pour prévenir d'une possible pollution du tas (heap pollution). A cause du type erasure, le compilateur ne peut pas vérifier le type d'objets utiliser dans les collections de l'exemple ci-dessous :

Exemple :
import java.util.List;
import java.util.ArrayList;
 
public class MaClasse {
 
  public static void main(String... args) {
    List liste = new ArrayList<String>();
    liste.add("1");
    List<Integer> entiers = liste;
    Integer i = entiers.get(0);
  }
}

Résultat :
C:\java>javac -Xlint:unchecked MaClasse.java
MaClasse.java:8: warning: [unchecked] unchecked call to add(E) as a member
of the raw type List
    liste.add("1");
             ^
  where E is a type-variable:
    E extends Object declared in interface List
MaClasse.java:9: warning: [unchecked] unchecked conversion
    List<Integer> entiers = liste;
                            ^
  required: List<Integer>
  found:    List
2 warnings
C:\java>java MaClasse
Exception in thread "main" java.lang.ClassCastException:
java.lang.String cannot be cast to java.lang.Integer
        at MaClasse.main(MaClasse.java:10)

L'utilisation de certains frameworks peut aussi générer des avertissements de type unchecked lorsque leurs API peuvent manipuler plusieurs types n'ayant aucun type en commun. C'est notamment le cas avec l'API Java EE JPA qui permet de manipuler potentiellement d'importe qu'elle type d'entité.

Exemple :
@SuppressWarnings("unchecked")
public List<Personne> obtenirPersonnesToutes(){
    Query query = entitymanager.createQuery("select p from Personne p");
    return (List<Personne>)query.getResultList();
}

Il est possible de désactiver ce type avertissement.

Exemple :
import java.util.List;
import java.util.ArrayList;
 
public class MaClasse {
 
  @SuppressWarnings("unchecked")
  public static void main(String... args) {
    List liste = new ArrayList();
    liste.add("element1");
  }
}

Résultat :
C:\java> javac -Xlint:unchecked MaClasse.java
 
C:\java>

La suppression d'un avertissement de type unchecked ne devrait être utilisée que si l'on est sûr que le code est type-safe.

Exemple ( code Java 5.0 ) :
import java.util.ArrayList;
import java.util.Collection;

public class MaClasse {
 
  public static void main(String[] args) {
    traiter(new ArrayList<Object>());
  }
 
  public static void traiter(Object object) {
    if (object instanceof Collection) {
      Collection<?> coll = (Collection<?>) object;
      calculer(coll);
    }
  }
 
  public static void calculer(Collection<Object> object) {
    System.out.println("Collection<Object>");
  }
 
  public static void calculer(Object object) {
    System.out.println("Object");
  }
}

Résultat :
C:\java>javac -Xlint:unchecked MaClasse.java
C:\java>java MaClasse
Object

Le code ci-dessus se compile sans avertissement mais c'est la surcharge qui attend un paramètre de type Object qui est invoquée. Pour que cela soit la surcharge qui attend un paramètre de type Collection, il faut préciser le type générique Object et donc effectuer un cast.

Exemple ( code Java 5.0 ) :
  public static void traiter(Object object) {
    if (object instanceof Collection) {
      Collection<Object> coll = (Collection<Object>) object;
      calculer(coll);
    }
  }

Résultat :
C:\java>javac -Xlint:unchecked MaClasse.java
MaClasse.java:12: warning: [unchecked] unchecked cast
     
Collection<Object> coll = (Collection<Object>) object;
                                                    ^
 
required: Collection<Object>
 
found:    Object
1 warning
C:\java>java MaClasse
Collection<Object>

Le compilateur émet un avertissement suite au cast. Cependant ce cast est sûr puisque ce dernier est conditionné par le fait que le paramètre soit de type Collection. Dans ce cas, il est possible de demander la suppression de l'avertissement.

Exemple ( code Java 5.0 ) :
  public static void traiter(Object object) {
    if (object instanceof Collection) {
      @SuppressWarnings("unchecked")
      Collection<Object> coll = (Collection<Object>) object;
      calculer(coll);
    }
  }

Résultat :
C:\java>javac -Xlint:unchecked MaClasse.java
C:\java>java MaClasse
Collection<Object>

Les avertissements de type unchecked sont importants et ne devraient pas être ignorés. Un tel avertissement implique la possibilité d'avoir une ClassCastException à l'exécution.

Il ne faut utiliser l'annotation @SuppressWarnings("unchecked") que si l'avertissement ne peut être résolu et que l'on soit sûr que le code est type-safe.

Ce n'est pas une bonne pratique de délibérément utiliser un type générique sans préciser le générique et d'utiliser l'annotation @SuppressWarnings

 

11.4.3.5. Les bonnes pratiques d'utilisation

La suppression des avertissements doit se faire avec d'extrêmes précautions.

Un avertissement permet au compilateur de signaler qu'il a trouvé quelque chose qui semble louche. Cela ne veut pas dire que c'est louche, mais ça y ressemble pour le compilateur.

Parfois, il est possible de modifier le code pour éliminer l'avertissement.

Parfois, le code est bon mais il n'est pas possible d'éviter un avertissement. Dans ce cas, très rare, il est possible de demander au compilateur d'ignorer l'avertissement. C'est un dernier recours qui ne doit être utilisé que si c'est justifié.

Cependant, il est fréquent de trouver des annotations @SuppressWarning utilisées simplement pour désactiver les avertissements afin d'améliorer artificiellement la qualité du code lié à la réduction du nombre d'avertissements. Il est préférable dans ce cas, de passer du temps pour résoudre l'avertissement lorsque c'est possible plutôt que d'ignorer systématiquement les avertissements.

Il ne faut donc pas ignorer un warning émis par le compilateur car il nous signale un problème potentiel même si celui-ci n'empêche pas la compilation. Par exemple, un avertissement de type unchecked est un risque potentiel de voir une exception de type ClassCastException au runtime.

Il est important de s'assurer qu'il n'est pas possible de corriger le code pour éviter l'émission d'un warning : si cela n'est pas possible ou le warning est vraiment injustifié alors il est possible de demander au compilateur de l'ignorer en utilisant l'annotation @SuppressWarnings.

L'annotation @SuppressWarning peut être appliquée sur une classe, un champ, une méthode, un constructeur, un paramètre ou une variable en locale. Il n'est pas recommandé d'utiliser l'annotation @SuppressWarnings au niveau d'une classe : il est préférable de l'utiliser sur l'élément le plus bas qui génère un avertissement. Ceci évite de masquer d'autres avertissements du même type pouvant être émis par des éléments sous-jacent.

Il est donc important de n'utiliser l'annotation @SuppressWarning qu'au niveau le plus bas, le plus prêt de l'origine du warning. Il est par exemple préférable de mettre l'annotation @SuppressWarnings sur une variable locale plutôt que sur la méthode qui la contienne.

Il est fortement recommandé de mettre un commentaire précisant les raisons de l'utilisation de @SuppressWarnings.

Il est fortement déconseillé d'utiliser @SuppressWarnings("all") car elle demande d'ignorer tous les warnings et ainsi masquer de futurs avertissements.

 

11.5. Les annotations communes (Common Annotations)

Les annotations communes sont définies par la JSR 250 et sont intégrées dans Java 6. Leur but est de définir des annotations couramment utilisées et ainsi d'éviter leur redéfinition pour chaque outil qui en aurait besoin.

Les annotations définies concernent :

 

11.5.1. L'annotation @Generated

De plus en plus d'outils ou de frameworks génèrent du code source pour faciliter la tâche des développeurs notamment pour des portions de code répétitives ayant peu de valeur ajoutée.

Le code ainsi généré peut être marqué avec l'annotation @Generated.

Exemple :
    @Generated(
        value = "entite.qui.a.genere.le.code", 
        comments = "commentaires", 
        date = "12 April  2008"
    )
    public void toolGeneratedCode(){
    }

L'attribut obligatoire value permet de préciser l'outil à l'origine de la génération

Les attributs facultatifs comments et date permettent respectivement de fournir un commentaire et la date de génération.

Cette annotation peut être utilisée sur toutes les déclarations d'entités.

 

11.5.2. Les annotations @Resource et @Resources

L'annotation @Resource définit une ressource requise par une classe. Typiquement, une ressource est par exemple un composant Java EE de type EJB ou JMS.

L'annotation @Resource possède plusieurs attributs :

Attribut

Description

authentificationType

Type d'authentification pour utiliser la ressource (Resource.AuthenticationType.CONTAINER ou Resource.AuthenticationType.APPLICATION)

description

Description de la ressource

mappedName

Nom de la ressource spécifique au serveur utilisé (non portable)

name

Nom JNDI de la ressource

shareable

Booléen qui précise si la ressource est partagée

type

Le type pleinement qualifié de la ressource


Cette annotation peut être utilisée sur une classe, un champ ou une méthode.

Lorsque l'annotation est utilisée sur une classe, elle correspond simplement à une déclaration des ressources qui seront requises à l'exécution.

Lorsque l'annotation est utilisée sur un champ ou une méthode, le serveur d'applications va injecter une référence sur la ressource correspondante. Pour cela, lors du chargement d'une application par le serveur d'applications, celui-ci recherche les annotations @Resource afin d'assigner une instance de la ressource correspondante.

Exemple :
@Resource(name="MaQueue", 
    type = "javax.jms.Queue", 
    shareable=false, 
    authenticationType=Resource.AuthenticationType.CONTAINER, 
    description="Queue de test"
)
private javax.jms.Queue maQueue;

L'annotation @Resources est simplement une collection d'annotation de type @Resource.

Exemple :
@Resources({
    @Resource(name = "maQueue" type = javax.jms.Queue),
    @Resource(name = "monTopic" type = javax.jms.Topic),
})

 

11.5.3. Les annotations @PostConstruct et @PreDestroy

Les annotations @PostConstruct et @PreDestroy permettent respectivement de désigner des méthodes qui seront exécutées après l'instanciation d'un objet et avant la destruction d'une instance.

Ces deux annotations ne peuvent être utilisées que sur des méthodes.

Ces annotations sont par exemple utiles dans Java EE car généralement un composant géré par le conteneur est instancié en utilisant le constructeur sans paramètre. Une méthode marquée avec l'annotation @PostConstruct peut alors être exécutée juste après l'appel au constructeur.

Une telle méthode doit respecter certaines règles :

L'annotation @PostConstruct est utilisée en général sur une méthode qui initialise des ressources en fonction du contexte.

Dans une même classe, chacune de ces annotations n'est utilisable que par une seule méthode.

 

11.6. Les annotations personnalisées

Java propose la possibilité de définir ses propres annotations. Pour cela, le langage possède un type dédié : le type d'annotation (annotation type).

Un type d'annotation est similaire à une classe et une annotation est similaire à une instance de classe.

 

11.6.1. La définition d'une annotation

Sur la plate-forme Java, une annotation est une interface lors de sa déclaration et est une instance d'une classe qui implémente cette interface lors de son utilisation.

La définition d'une annotation nécessite une syntaxe particulière utilisant le mot clé @interface. Une annotation se déclare donc de façon similaire à une interface.

Exemple : le fichier MonAnnotation.java
package fr.jmdoudoux.dej.annotations;
 
public @interface MonAnnotation {
 
}

Une fois compilée, cette annotation peut être utilisée dans le code. Pour utiliser une annotation, il faut importer l'annotation et l'appeler dans le code en la faisant précéder du caractère @.

Exemple :
package fr.jmdoudoux.dej.annotations;
 
@MonAnnotation
public class MaCLasse {
 
}

Si l'annotation est définie dans un autre package, il faut utiliser la syntaxe pleinement qualifiée du nom de l'annotation ou ajouter une clause import pour le package.

Il est possible d'ajouter des membres à l'annotation simplement en définissant une méthode dont le nom correspond au nom de l'attribut en paramètre de l'annotation.

Exemple :
package fr.jmdoudoux.dej.annotations;
 
public @interface MonAnnotation {
  String arg1();
  String arg2();
}
 
package fr.jmdoudoux.dej.annotations;
 
@MonAnnotation(arg1="valeur1", arg2="valeur2")
public class MaCLasse {
 
}

Les types utilisables sont les chaînes de caractères, les types primitifs, les énumérations, les annotations, les chaînes de caractères, le type Class.

Il est possible de définir un membre comme étant un tableau à une seule dimension d'un des types utilisables.

Exemple :
package fr.jmdoudoux.dej.annotations
 
public @interface MonAnnotation {
    String arg1();
    String[] arg2();
    String arg3();
}

Il est possible de définir une valeur par défaut, ce qui rend l'indication du membre optionnelle. Cette valeur est précisée en la faisant précéder du mot clé default.

Exemple :
package fr.jmdoudoux.dej.annotations;
 
public @interface MonAnnotation {
  String arg1() default "";
  String[] arg2();
  String arg3();
}

La valeur par défaut d'un tableau utilise une syntaxe raccourcie.

Exemple :
package fr.jmdoudoux.dej.annotations
 
public @interface MonAnnotation {
    String arg1();
    String[] arg2() default {"chaine1", "chaine2" };
    String arg3();
}

Il est possible de définir une énumération comme type pour un attribut

Exemple :
package fr.jmdoudoux.dej.annotations;
 
public @interface MonAnnotation {
  public enum Niveau {DEBUTANT, CONFIRME, EXPERT} ;
  String arg1() default "";
  String[] arg2();
  String arg3();
  Niveau niveau() default Niveau.DEBUTANT;
 
}

 

11.6.2. Les annotations pour les annotations

La version 5 de Java propose quatre annotations dédiées aux types d'annotations qui permettent de fournir des informations sur l'utilisation.

Ces annotations sont définies dans le package java.lang.annotation

 

11.6.2.1. L'annotation @Target

L'annotation @Target permet de préciser les entités sur lesquelles l'annotation sera utilisable. Cette annotation attend comme valeur un tableau de valeurs issues de l'énumération ElementType.

Valeur de l'énumération

Rôle

ANNOTATION_TYPE

Types d'annotation

CONSTRUCTOR

Constructeurs

LOCAL_VARIABLE

Variables locales

FIELD

Champs

METHOD

Méthodes hors constructeurs

PACKAGE

Packages

PARAMETER

Paramètres d'une méthode ou d'un constructeur

TYPE

Classes, interfaces, énumérations, types d'annotation


Si une annotation est utilisée sur une entité non précisée par l'annotation, alors une erreur est émise lors de la compilation.

Exemple :
package fr.jmdoudoux.dej.annotations;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
 
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR })
public @interface MonAnnotation {
  String arg1() default "";
  String arg2();
}
 
package fr.jmdoudoux.dej.annotations;
 
@MonAnnotation(arg1="valeur1", arg2="valeur2")
public class MaCLasse {
 
}


Résultat de la compilation :
C:\Documents and Settings\jmd\workspace\Tests>javac com/jmdoudoux/test/annotatio
ns/MaClasse.java
com\jmdoudoux\test\annotations\MaClasse.java:3: annotation type not applicable t
o this kind of declaration
@MonAnnotation(arg1="valeur1", arg2="valeur2")
^
1 error

 

11.6.2.2. L'annotation @Retention

Cette annotation permet de préciser à quel niveau les informations concernant l'annotation seront conservées. Cette annotation attend comme valeur un élément de l'énumération RetentionPolicy.

Enumération

Rôle

RetentionPolicy.SOURCE

informations conservées dans le code source uniquement (fichier .java) : le compilateur les ignore

RetentionPolicy.CLASS

informations conservées dans le code source et le bytecode (fichiers .java et .class)

RetentionPolicy.RUNTIME

informations conservées dans le code source et le bytecode : elles sont disponibles à l'exécution par introspection


Cette annotation permet de déterminer de quelle façon l'annotation pourra être exploitée.

Exemple :
@Retention(RetentionPolicy.RUNTIME)

 

11.6.2.3. L'annotation @Documented

L'annotation @Documented permet de demander l'intégration de l'annotation dans la documentation générée par Javadoc.

Par défaut, les annotations ne sont pas intégrées dans la documentation des classes annotées.

Exemple :
package fr.jmdoudoux.dej.annotations;
 
import java.lang.annotation.Documented;
 
@Documented
public @interface MonAnnotation {
  String arg1() default "";
  String arg2();
}

 

11.6.2.4. L'annotation @Inherited

L'annotation @Inherited permet de demander l'héritage d'une annotation aux classes filles de la classe mère sur laquelle elle s'applique.

Si une classe mère est annotée avec une annotation elle-même annotée avec @Inherited alors toutes les classes filles sont automatiquement annotées avec cette annotation.

 

11.7. L'exploitation des annotations

Pour être profitables, les annotations ajoutées dans le code source doivent être exploitées par un ou plusieurs outils.

La déclaration et l'utilisation d'annotations sont relativement simples par contre leur exploitation pour permettre la production de fichiers est moins triviale.

Cette exploitation peut se faire de plusieurs manières

 

11.7.1. L'exploitation des annotations dans un Doclet

Pour des traitements simples, il est possible de définir un Doclet et de le traiter avec l'outil Javadoc pour utiliser les annotations.

L'API Doclet est définit dans le package com.sun.javadoc. Ce package est dans le fichier tools.jar fourni avec le JDK.

L'API Doclet définit des interfaces pour chaque entité pouvant être utilisée dans le code source.

La méthode annotation() de l'interface ProgramElementDoc permet d'obtenir un tableau de type AnnotationDesc.

L'interface AnnotationDesc représente une annotation. Elle définit deux méthodes :

Méthode

Rôle

AnnotationTypeDoc annotationType()

Renvoyer le type d'annotation

AnnotationDesc.ElementValuePair[] elementValues()

Renvoyer les éléments de l'annotation


L'interface AnnotationTypeDoc représente un type d'annotation. Elle ne définit qu'une seule méthode :

Méthode

Rôle

AnnotationTypeElementDoc[] elements()

Renvoyer les éléments d'un type d'annotation


L'interface AnnotationTypeElementDoc représente un élément d'un type d'annotation. Elle ne définit qu'une seule méthode :

Méthode

Rôle

AnnotationValue defaultValue()

Renvoyer la valeur par défaut de l'élément d'un type d'annotation


L'interface AnnotationValue représente la valeur d'un élément d'un type d'annotation. Elle définit deux méthodes :

Méthode

Rôle

String toString()

Renvoyer la valeur sous forme d'une chaîne de caractères

Object value()

Renvoyer la valeur


Pour créer un Doclet, il faut définir une classe qui contienne une méthode ayant pour signature public static boolean start (RootDoc rootDoc).

Pour utiliser le Doclet, il faut compiler la classe qui l'encapsule et utiliser l'outil javadoc avec l'option -doclet suivi du nom de la classe.

 

11.7.2. L'exploitation des annotations avec l'outil Apt

La version 5 du JDK fournit l'outil apt pour le traitement des annotations.

L'outil apt qui signifie annotation processing tool est l'outil le plus polyvalent en Java 5 pour exploiter les annotations.

Apt assure la compilation des classes et permet en simultané le traitement des annotations par des processeurs d'annotations créés par le développeur.

Les processeurs d'annotations peuvent générer de nouveaux fichiers sources, pouvant eux-mêmes contenir des annotations. Apt traite alors récursivement les fichiers générés jusqu'à ce qu'il n'y ait plus d'annotation à traiter et de classe à compiler.

Cette section va créer un processeur pour l'annotation personnalisée Todo

Exemple : l'annotation personnalisée Todo
package fr.jmdoudoux.dej.annotations;
 
import java.lang.annotation.Documented;
 
@Documented
public @interface Todo {
 
  public enum Importance {
    MINEURE, IMPORTANT, MAJEUR, CRITIQUE
  };
 
  Importance importance() default Importance.MINEURE;
 
  String[] description();
 
  String assigneA();
 
  String dateAssignation();
}

Apt et l'API à utiliser de concert ne sont disponibles qu'avec le JDK : ils ne sont pas fournis avec le JRE.

Les packages de l'API sont dans le fichier lib/tools.jar du JDK : cette bibliothèque doit donc être ajoutée au classpath lors de la mise en oeuvre d'apt.

L'API est composée de deux grandes parties :

L'API est contenue dans plusieurs sous-packages de com.sun.mirror notamment :

Un processeur d'annotations est une classe qui implémente l'interface com.sun.mirror.apt.AnnotationProcessor. Cette interface ne définit qu'une seule méthode process() qui va contenir les traitements à réaliser pour une annotation.

Il faut fournir un constructeur qui attend en paramètre un objet de type com.sun.mirror.apt.AnnotationProcessorEnvironment : ce constructeur sera appelé par une fabrique pour en créer une instance.

L'interface AnnotationProcessorEnvironment fournit des méthodes pour obtenir des informations sur l'environnement d'exécution des traitements des annotations et créer de nouveaux fichiers pendant les traitements.

L'interface Declaration permet d'obtenir des informations sur une entité :

Méthode

Rôle

<A extends Annotation> getAnnotation(Class<A> annotationType)

Renvoie une annotation d'un certain type associée à l'entité

Collection<AnnotationMirror> getAnnotationMirrors()

Renvoie les annotations associées à l'entité

String getDocComment()

Renvoie le texte des commentaires de documentations Javadoc associés à l'entité

Collection<Modifier> getModifiers()

Renvoie les modificateurs de l'entité

SourcePosition getPosition()

Renvoie la position de la déclaration dans le code source

String getSimpleName()

Renvoie le nom de la déclaration


De nombreuses interfaces héritent de l'interface Declaration : AnnotationTypeDeclaration, AnnotationTypeElementDeclaration, ClassDeclaration, ConstructorDeclaration, EnumConstantDeclaration, EnumDeclaration, ExecutableDeclaration, FieldDeclaration, InterfaceDeclaration, MemberDeclaration, MethodDeclaration, PackageDeclaration, ParameterDeclaration, TypeDeclaration, TypeParameterDeclaration

Chacune de ces interfaces propose des méthodes pour obtenir des informations sur la déclaration et sur le type concernés.

L'interface TypeMirror permet d'obtenir des informations sur un type utilisé dans une déclaration.

De nombreuses interfaces héritent de l'interface TypeMirror : AnnotationType, ArrayType, ClassType, DeclaredType, EnumType, InterfaceType, PrimitiveType, ReferenceType, TypeVariable, VoidType, WildcardType.

La classe com.sun.mirror.util.DeclarationFilter permet de définir un filtre des entités annotées avec les annotations concernées par les traitements du processeur. Il suffit de créer une instance de cette classe en ayant redéfini sa méthode match(). Cette méthode renvoie un booléen qui précise si l'entité fournie en paramètre sous la forme d'un objet de type Declaration est annotée avec une des annotations concernées par le processeur.

Exemple :
package fr.jmdoudoux.dej.annotations.outils;
 
import java.util.Collection;
 
import fr.jmdoudoux.dej.annotations.Todo;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.Declaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.DeclarationFilter;
 
public class TodoAnnotationProcessor implements AnnotationProcessor {
  private final AnnotationProcessorEnvironment env;
 
  public TodoAnnotationProcessor(AnnotationProcessorEnvironment env) {
    this.env = env;
  }
 
  public void process() {
    // Création d'un filtre pour ne retenir que les déclarations annotées avec Todo
    DeclarationFilter annFilter = new DeclarationFilter() {
      public boolean matches(
          Declaration d) {
        return d.getAnnotation(Todo.class) != null;
      }
    };
 
    // Recherche des entités annotées avec Todo
    Collection<TypeDeclaration> types = annFilter.filter(env.getSpecifiedTypeDeclarations());
    for (TypeDeclaration typeDecl : types) {
      System.out.println("class name: " + typeDecl.getSimpleName());
 
      Todo todo = typeDecl.getAnnotation(Todo.class);
 
      System.out.println("description : ");
      for (String desc : todo.description()) {
        System.out.println(desc);
      }
      System.out.println("");
    }
  }
}

Il faut créer une fabrique de processeurs d'annotations : cette fabrique est en charge d'instancier des processeurs d'annotations pour un ou plusieurs types d'annotations. La fabrique doit implémenter l'interface com.sun.mirror.apt.AnnotationProcessorFactory.

L'interface AnnotationProcessorFactory déclare trois méthodes :

Méthode

Rôle

AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env)

Renvoyer un processeur d'annotations pour l'ensemble de types d'annotations fournis en paramètres.

Collection<String> supportedAnnotationTypes()

Renvoyer une collection des types d'annotations dont un processeur peut être instancié par la fabrique

Collection<String> supportedOptions()

Renvoyer une collection des options supportées par la fabrique ou par les processeurs d'annotations créés par la fabrique


Exemple :
package fr.jmdoudoux.dej.annotations.outils;
 
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
 
import java.util.Collection;
import java.util.Set;
import java.util.Collections;
import java.util.Arrays;
 
public class TodoAnnotationProcessorFactory implements AnnotationProcessorFactory {
  private static final Collection<String> supportedAnnotations = 
    Collections.unmodifiableCollection(Arrays
      .asList("fr.jmdoudoux.dej.annotations.Todo"));
 
  private static final Collection<String> supportedOptions     = Collections.emptySet();
 
  public Collection<String> supportedOptions() {
    return supportedOptions;
  }
 
  public Collection<String> supportedAnnotationTypes() {
    return supportedAnnotations;
  }
 
  public AnnotationProcessor getProcessorFor(
      Set<AnnotationTypeDeclaration> atds,
      AnnotationProcessorEnvironment env) {
    return new TodoAnnotationProcessor(env);
  }
}

Dans l'exemple ci-dessus, aucune option n'est supportée et la fabrique ne prend en charge que l'annotation personnalisée Todo.

Pour mettre en oeuvre les traitements des annotations, il faut que le code source utilise ces annotations.

Exemple : une classe annotée avec l'annotation Todo
package fr.jmdoudoux.dej;
 
import fr.jmdoudoux.dej.annotations.Todo;
import fr.jmdoudoux.dej.annotations.Todo.Importance;
 
@Todo(importance = Importance.CRITIQUE, 
      description = "Corriger le bug dans le calcul", 
      assigneA = "JMD", 
      dateAssignation = "11-11-2007")
public class MaClasse {
 
}


Exemple : une autre classe annotée avec l'annotation Todo
package fr.jmdoudoux.dej;
 
import fr.jmdoudoux.dej.annotations.Todo;
import fr.jmdoudoux.dej.annotations.Todo.Importance;
 
@Todo(importance = Importance.MAJEUR, 
      description = "Ajouter le traitement des erreurs", 
      assigneA = "JMD", 
      dateAssignation = "07-11-2007")
public class MaClasse1 {
 
}

Pour utiliser apt, il faut que le classpath contienne la bibliothèque tools.jar fournie avec le JDK et les classes de traitements des annotations (fabrique et processeur d'annotations).

L'option -factory permet de préciser la fabrique à utiliser.

Résultat de l'exécution d'apt
C:\Documents and Settings\jmd\workspace\Tests>apt -cp ".;./bin;C:/Program Files/
Java/jdk1.5.0_07/lib/tools.jar" -factory fr.jmdoudoux.dej.annotations.outils.T
odoAnnotationProcessorFactory com/jmdoudoux/test/*.java
class name: MaClasse
description :
Corriger le bug dans le calcul
 
class name: MaClasse1
description :
Ajouter le traitement des erreurs

A partir de l'objet de type AnnotationProcessorEnvironment, il est possible d'obtenir un objet de type com.sun.miror.apt.Filer qui encapsule un nouveau fichier créé par le processeur d'annotations.

L'interface Filer propose quatre méthodes pour créer différents types de fichiers :

Méthode

Rôle

OutputStream createBinaryFile(Filer.Location loc, String pkg, File relPath)

Créer un nouveau fichier binaire et renvoyer un objet de type Stream pour écrire son contenu

OutputStream createClassFile(String name)

Créer un nouveau fichier .class et renvoyer un objet de type Stream pour écrire son contenu

PrintWriter createSourceFile(String name)

Créer un nouveau fichier texte contenant du code source et renvoyer un objet de type Writer pour écrire son contenu

PrintWriter createTextFile(Filer.Location loc, String pkg, File relPath, String charsetName)

Créer un nouveau fichier texte et renvoyer un objet de type Writer pour écrire son contenu


L'énumération Filter.Location permet de préciser si le nouveau fichier est créé dans la branche source (SOURCE_TREE) ou dans la branche compilée (CLASS_TREE).

Exemple :
package fr.jmdoudoux.dej.annotations.outils;
 
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
 
import fr.jmdoudoux.dej.annotations.Todo;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.Filer;
import com.sun.mirror.declaration.Declaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.DeclarationFilter;
 
public class TodoAnnotationProcessor implements AnnotationProcessor {
  private final AnnotationProcessorEnvironment env;
 
  public TodoAnnotationProcessor(AnnotationProcessorEnvironment env) {
    this.env = env;
  }
 
  public void process() {
    // Création d'un filtre pour ne retenir que les déclarations annotées avec
    // Todo
    DeclarationFilter annFilter = new DeclarationFilter() {
      public boolean matches(
          Declaration d) {
        return d.getAnnotation(Todo.class) != null;
      }
    };
 
    Filer f = this.env.getFiler();
    PrintWriter out;
    try {
      out = f.createTextFile(Filer.Location.SOURCE_TREE, "", new File("todo.txt"), null);
 
      // Recherche des entités annotées avec Todo
      Collection<TypeDeclaration> types = annFilter.filter(env.getSpecifiedTypeDeclarations());
      for (TypeDeclaration typeDecl : types) {
        out.println("class name: " + typeDecl.getSimpleName());
 
        Todo todo = typeDecl.getAnnotation(Todo.class);
 
        out.println("description : ");
        for (String desc : todo.description()) {
          out.println(desc);
        }
        out.println("");
      }
 
      out.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}


Résultat de l'exécution
C:\Documents and Settings\jm\workspace\Tests>apt -cp ".;./bin;C:/Program Files/
Java/jdk1.5.0_07/lib/tools.jar" -factory fr.jmdoudoux.dej.annotations.outils.T
odoAnnotationProcessorFactory com/jmdoudoux/test/*.java
 
C:\Documents and Settings\jm\workspace\Tests>dir
 Volume in drive C has no label.
 Volume Serial Number is 1D31-4F67
 
 Directory of C:\Documents and Settings\jmd\workspace\Tests
 
19/11/2007  08:39    <DIR>          .
19/11/2007  08:39    <DIR>          ..
16/11/2007  08:15               433 .classpath
31/10/2006  14:06               381 .project
14/09/2007  12:45    <DIR>          .settings
16/11/2007  08:15    <DIR>          bin
02/10/2007  15:22               854 build.xml
29/06/2007  07:12    <DIR>          com
15/11/2007  13:01    <DIR>          doc
19/11/2007  08:39               148 todo.txt
              8 File(s)          1 812 bytes
               6 Dir(s)  66 885 595 136 bytes free
 
C:\Documents and Settings\jm\workspace\Tests>type todo.txt
class name: MaClasse
description :
Corriger le bug dans le calcul
 
class name: MaClasse1
description :
Ajouter le traitement des erreurs

Concernant les entités à traiter, l'API Mirror fournit de nombreuses autres fonctionnalités qui permettent de rendre très riche le traitement des annotations. Parmi ces fonctionnalités, il y a le parcours des sources par des classes mettant en oeuvre le motif de conception visiteur.

 

11.7.3. L'exploitation des annotations par introspection

Pour qu'une annotation soit exploitée à l'exécution, il est nécessaire qu'elle soit annotée avec une RetentionPolicy à la valeur RUNTIME.

Exemple :
package fr.jmdoudoux.dej.annotations;
 
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
 
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Todo {
 
  public enum Importance {
    MINEURE, IMPORTANT, MAJEUR, CRITIQUE
  };
 
  Importance importance() default Importance.MINEURE;
 
  String[] description();
 
  String assigneA();
 
  String dateAssignation();
}

L'interface java.lang.reflect.AnnotatedElement définit les méthodes pour le traitement des annotations par introspection :

Méthode

Rôle

<T extends Annotation> getAnnotation(Class<T>)

Renvoyer l'annotation si le type fourni en paramètre est utilisé sur l'entité, sinon null

Annotation[] getAnnotations()

Renvoyer un tableau de toutes les annotations de l'entité. Renvoie un tableau vide si aucune annotation n'est concernée

Annotation[] getDeclaredAnnotations()

Renvoyer un tableau des annotations directement associées à l'entité (en ignorant donc les annotations héritées). Renvoie un tableau vide si aucune annotation n'est concernée

boolean isAnnotationPresent(Class< ? extends Annotation>)

Renvoyer true si l'annotation dont le type est fourni en paramètre est utilisé sur l'entité. Cette méthode est particulièrement utile dans le traitement des annotations de type marqueur.


Plusieurs classes du package java.lang implémentent l'interface AnnotatedElement : AccessibleObject, Class, Constructor, Field, Method et Package

Exemple :
package fr.jmdoudoux.dej;
 
import java.lang.reflect.Method;
 
import fr.jmdoudoux.dej.annotations.Todo;
import fr.jmdoudoux.dej.annotations.Todo.Importance;
 
@Todo(importance = Importance.CRITIQUE, 
      description = "Corriger le bug dans le calcul", 
      assigneA = "JMD", 
      dateAssignation = "11-11-2007")
public class TestInstrospectionAnnotation {
 
  public static void main(
      String[] args) {
    Todo todo = null;
 
    // traitement annotation sur la classe
    Class classe = TestInstrospectionAnnotation.class;
    todo = (Todo) classe.getAnnotation(Todo.class);
    if (todo != null) {
      System.out.println("classe "+classe.getName());
      System.out.println("  ["+todo.importance()+"]"+" ("+todo.assigneA()
	    +" le "+todo.dateAssignation()+")");
      for(String desc : todo.description()) {
        System.out.println("     _ "+desc);
      }
    }
    
    // traitement annotation sur les méthodes de la classe    
    for(Method m : TestInstrospectionAnnotation.class.getMethods()) {
      todo = (Todo) m.getAnnotation(Todo.class);
      if (todo != null) {
        System.out.println("méthode "+m.getName());
        System.out.println("  ["+todo.importance()+"]"+" ("+todo.assigneA()
		  +" le "+todo.dateAssignation()+")");
        for(String desc : todo.description()) {
          System.out.println("     _ "+desc);
        }
      }
    }
  }
 
  @Todo(importance = Importance.MAJEUR, 
        description = "Implémenter la methode", 
        assigneA = "JMD", 
        dateAssignation = "11-11-2007")
  public void methode1() {
    
  }
  
  @Todo(importance = Importance.MINEURE, 
        description = {"Compléter la methode", "Améliorer les logs"}, 
        assigneA = "JMD", 
        dateAssignation = "12-11-2007")
  public void methode2() {
    
  }
 
}


Résultat d'exécution :
classe fr.jmdoudoux.dej.TestInstrospectionAnnotation
  [CRITIQUE] (JMD le 11-11-2007)
     _ Corriger le bug dans le calcul
méthode methode1
  [MAJEUR] (JMD le 11-11-2007)
     _ Implémenter la methode
méthode methode2
  [MINEURE] (JMD le 12-11-2007)
     _ Compléter la methode
     _ Améliorer les logs

Pour obtenir les annotations sur les paramètres d'un constructeur ou d'une méthode, il faut utiliser la méthode getParameterAnnotations() des classes Constructor ou Method qui renvoie un objet de type Annotation[][]. La première dimension du tableau concerne les paramètres dans leur ordre de déclaration. La seconde dimension contient les annotations de chaque paramètre.

 

11.7.4. L'exploitation des annotations par le compilateur Java

Dans la version 6 de Java SE, la prise en compte des annotations est intégrée dans le compilateur : ceci permet un traitement à la compilation des annotations sans avoir recours à un outil tiers comme apt.

Une nouvelle API a été définie par la JSR 269 (Pluggable annotations processing API) et ajoutée dans la package javax.annotation.processing.

Cette API est détaillée dans la section suivante.

 

11.8. L'API Pluggable Annotation Processing

La version 6 de Java apporte plusieurs améliorations dans le traitement des annotations notamment l'intégration de ces traitements directement dans le compilateur javac grâce à une nouvelle API dédiée.

L'API Pluggable Annotation Processing est définie dans la JSR 269. Elle permet un traitement des annotations directement par le compilateur en proposant une API aux développeurs pour traiter les annotations incluses dans le code source.

Apt et son API proposaient déjà une solution à ces traitements mais cette API standardise le traitement des annotations au moment de la compilation. Il n'est donc plus nécessaire d'utiliser un outil tiers post compilation pour traiter les annotations à la compilation.

Dans les exemples de cette section, les classes suivantes seront utilisées :

Exemple : MaClasse.java
package fr.jmdoudoux.dej;
 
import fr.jmdoudoux.dej.annotations.Todo;
import fr.jmdoudoux.dej.annotations.Todo.Importance;
 
@Todo(importance = Importance.CRITIQUE, 
      description = "Corriger le bug dans le calcul", 
      assigneA = "JMD", 
      dateAssignation = "11-11-2007")
public class MaClasse {
 
}


Exemple : MaClasse1.java
package fr.jmdoudoux.dej;
 
import fr.jmdoudoux.dej.annotations.Todo;
import fr.jmdoudoux.dej.annotations.Todo.Importance;
 
@Todo(importance = Importance.MAJEUR, 
      description = "Ajouter le traitement des erreurs", 
      assigneA = "JMD", 
      dateAssignation = "07-11-2007")
public class MaClasse1 {
 
}


Exemple : MaClasse3.java
package fr.jmdoudoux.dej;
 
@Deprecated
public class MaClasse3 {
 
}

Un exemple de mise en oeuvre de l'API est aussi fourni avec le JDK dans le sous-répertoire sample/javac/processing.

 

11.8.1. Les processeurs d'annotations

La mise en oeuvre de cette API nécessite l'utilisation des packages javax.annotation.processing, javax.lang.model et javax.tools.

Un processeur d'annotations doit implémenter l'interface Processor. Le traitement des annotations se fait en plusieurs passes (round). A chaque passe le processeur est appelé pour traiter des classes qui peuvent avoir été générées lors de la précédente passe. Lors de la première passe, ce sont les classes fournies initialement qui sont traitées.

L'interface javax.annotation.processing.Processor définit les méthodes d'un processeur d'annotations. Pour définir un processeur, il est possible de créer une classe qui implémente l'interface Processor mais le plus simple est d'hériter de la classe abstraite javax.annotation.processing.AbstractProcessor.

La classe AbstractProcessor contient une variable nommée processingEnv de type ProcessingEnvironment. La classe ProcessingEnvironment permet d'obtenir des instances de classes qui permettent des interactions avec l'extérieur du processeur ou fournissent des utilitaires :

La méthode getRootElements() renvoie les classes Java qui seront traitées par le processeur dans cette passe.

La méthode la plus importante est la méthode process() : c'est elle qui va contenir les traitements exécutés par le processeur. Elle possède deux paramètres :

Deux annotations sont dédiées aux processeurs d'annotations et doivent être utilisées sur la classe du processeur :

Exemple :
package fr.jmdoudoux.dej.annotations.outils;
 
import java.util.Set;
 
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
 
import fr.jmdoudoux.dej.annotations.Todo;
 
@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class TodoProcessor extends AbstractProcessor {
 
  @Override
  public boolean process(
      Set<? extends TypeElement> annotations,
      RoundEnvironment roundEnv) {
 
    Messager messager = processingEnv.getMessager();
 
    for (TypeElement te : annotations) {
      messager.printMessage(Kind.NOTE, "Traitement annotation " 
	    + te.getQualifiedName());
 
      for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
        messager.printMessage(Kind.NOTE, "  Traitement élément " 
		  + element.getSimpleName());
        Todo todo = element.getAnnotation(Todo.class);
 
        if (todo != null) {
          messager.printMessage(Kind.NOTE, "  affecté le " + todo.dateAssignation() 
		    + " a " + todo.assigneA());
        }
      }
    }
 
    return true;
  }
}

 

11.8.2. L'utilisation des processeurs par le compilateur

Le compilateur javac est enrichi avec plusieurs options concernant le traitement des annotations :

Option

Rôle

-processor

permet de préciser le nom pleinement qualifié du processeur à utiliser

-proc

vérifie si le traitement des annotations et/ou la compilation sont effectués

-processorpath

classpath des processeurs d'annotations

-A

permet de passer des options aux processeurs d'annotations sous la forme de paires cle=valeur

-XprintRounds

option non standard qui permet d'afficher des informations sur le traitement des annotations par les processeurs

-XprintProcessorInfo

option non standard qui affiche la liste des annotations qui seront traitées par les processeurs d'annotations


Le compilateur fait appel à la méthode process() du processeur en lui passant en paramètre l'ensemble des annotations trouvées par le compilateur dans le code source.

Résultat :
C:\Documents and Settings\jm\workspace\TestAnnotations>javac -cp ".;./bin;C:/Pr
ogram Files/Java/jdk1.6.0/lib/tools.jar" -processor fr.jmdoudoux.dej.annotati
ons.outils.TodoProcessor com/jmdoudoux/tests/*.java
Note: Traitement annotation fr.jmdoudoux.dej.annotations.Todo
Note:   Traitement élément MaClasse
Note:   affecte le 11-11-2007 a JMD
Note:   Traitement élément MaClasse1
Note:   affecte le 07-11-2007 a JMD
Note: Traitement annotation java.lang.Deprecated
Note:   Traitement élément MaClasse3

 

11.8.3. La création de nouveaux fichiers

La classe Filer permet de créer des fichiers lors du traitement des annotations.

Exemple :
package fr.jmdoudoux.dej.annotations.outils;
 
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
 
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.StandardLocation;
import javax.tools.Diagnostic.Kind;
 
import fr.jmdoudoux.dej.annotations.Todo;
 
@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class TodoProcessor2 extends AbstractProcessor {
 
  @Override
  public boolean process(
      Set<? extends TypeElement> annotations,
      RoundEnvironment roundEnv) {
 
    Filer filer = processingEnv.getFiler();
    Messager messager = processingEnv.getMessager();
    Elements eltUtils = processingEnv.getElementUtils();
    if (!roundEnv.processingOver()) {
      TypeElement elementTodo = 
	    eltUtils.getTypeElement("fr.jmdoudoux.dej.annotations.Todo");
      Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(elementTodo);
      if (!elements.isEmpty())
        try {
          messager.printMessage(Kind.NOTE, "Création du fichier Todo");
          PrintWriter pw = new PrintWriter(filer.createResource(
		      StandardLocation.SOURCE_OUTPUT, "", "Todo.txt")
              .openOutputStream());
          // .createSourceFile("Todo").openOutputStream());
          pw.println("Liste des todos\n");
 
          for (Element element : elements) {
            pw.println("\nélément:" + element.getSimpleName());
            Todo todo = (Todo) element.getAnnotation(Todo.class);
            pw.println("  affecté le " + todo.dateAssignation() 
			  + " a " + todo.assigneA());
            pw.println("  description : ");
            for (String desc : todo.description()) {
              pw.println("    " + desc);
            }
          }
 
          pw.close();
        } catch (IOException ioe) {
          messager.printMessage(Kind.ERROR, ioe.getMessage());
        }
      else
        messager.printMessage(Kind.NOTE, "Rien à faire");
    } else
      messager.printMessage(Kind.NOTE, "Fin des traitements");
 
    return true;
  }
}


Résultat :
C:\Documents and Settings\jmd\workspace\TestAnnotations>javac -cp ".;./bin;C:/Pr
ogram Files/Java/jdk1.6.0/lib/tools.jar" -processor fr.jmdoudoux.dej.annotati
ons.outils.TodoProcessor2 com/jmdoudoux/tests/*.java
Note: Création du fichier Todo
Note: Fin des traitements
 
C:\Documents and Settings\jmd\workspace\TestAnnotations>type Todo.txt
Liste des todos
 
 
élément:MaClasse
  affecté le 11-11-2007 a JMD
  description :
    Corriger le bug dans le calcul
 
element:MaClasse1
  affecté le 07-11-2007 a JMD
  description :
    Ajouter le traitement des erreurs

 

11.9. Les ressources relatives aux annotations

La JSR 175 A Metadata Facility for the JavaTM Programming Language

La JSR 269 Pluggable Annotation Processing API

La JSR 250 Common Annotations

La page des annotations dans le tutorial Java

Le projet open source XDoclet qui propose la génération de code à partir d'attributs dans le code


10. Les énumérations (type enum) 12. Les expressions lambda Imprimer Index Index avec sommaire Télécharger le PDF    
Développons en Java
v 2.30   Copyright (C) 1999-2022 .