IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]


 

8. JDK 1.5 (nom de code Tiger)

 

chapitre   8

 

Niveau : niveau 2 Elémentaire 

 

La version 1.5 de Java dont le nom de code est Tiger est développée par la JSR 176.

La version utilisée dans ce chapitre est la version bêta 1.

Exemple :
C:\>java -version
java version "1.5.0-beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta-b32c)
Java HotSpot(TM) Client VM (build 1.5.0-beta-b32c, mixed mode)

La version 1.5 de Java apporte de nombreuses évolutions qui peuvent être classées dans deux catégories :

  • Les évolutions sur la syntaxe du langage
  • Les évolutions sur les API : mises à jour d'API existantes, intégration d'API dans le SDK

Ce chapitre va détailler les nombreuses évolutions sur la syntaxe du langage. Il contient plusieurs sections :

 

8.1. Les nouveautés du langage Java version 1.5

Depuis sa première version et jusqu'à sa version 1.5, le langage Java lui-même n'a que très peu évolué : la version 1.1 a ajouté les classes internes et la version 1.4 les assertions.

Les évolutions de ces différentes versions concernaient donc essentiellement les API de la bibliothèque standard (core) de Java.

La version 1.5 peut être considérée comme une petite révolution pour Java car elle apporte énormément d'améliorations sur le langage. Toutes ces évolutions sont déjà présentes dans différents autres langages notamment C#.

Le but principal de ces ajouts est de faciliter le développement d'applications avec Java en simplifiant l'écriture et la lecture du code.

Un code utilisant les nouvelles fonctionnalités de Java 1.5 ne pourra pas être exécuté dans une version antérieure de la JVM.

Pour compiler des classes utilisant les nouvelles fonctionnalités de la version 1.5, il faut utiliser les options -target 1.5 et -source 1.5 de l'outil javac. Par défaut, ce compilateur utilise les spécifications 1.4 de la plate-forme.

 

8.2. L'autoboxing (boxing/unboxing)

L'autoboxing permet de transformer automatiquement une variable de type primitif en un objet du type du wrapper correspondant. L'unboxing est l'opération inverse. Cette nouvelle fonctionnalité est spécifiée dans la JSR 201.

Par exemple, jusqu'à la version 1.4 de Java pour ajouter des entiers dans une collection, il était nécessaire d'encapsuler chaque valeur dans un objet de type Integer.

Exemple :
import java.util.*;

public class TestAutoboxingOld {

  public static void main(String[] args) {

    List liste = new ArrayList();
    Integer valeur = null;
    for(int i = 0; i < 10; i++) {
      valeur = new Integer(i);
      liste.add(valeur);
    }
  }
}

Avec la version 1.5, l'encapsulation de la valeur dans un objet n'est plus obligatoire car elle sera réalisée automatiquement par le compilateur.

Exemple (java 1.5) :
import java.util.*;

public class TestAutoboxing {

  public static void main(String[] args) {
    List liste = new ArrayList();
    for(int i = 0; i < 10; i++) {
      liste.add(i);
    }    
  }
}

 

8.3. Les importations statiques

Jusqu'à la version 1.4 de Java, pour utiliser un membre statique d'une classe, il fallait obligatoirement préfixer ce membre par le nom de la classe qui le contient.

Par exemple, pour utiliser la constante Pi définie dans la classe java.lang.Math, il est nécessaire d'utiliser Math.PI

Exemple :
public class TestStaticImportOld {

  public static void main(String[] args) {
    System.out.println(Math.PI);
    System.out.println(Math.sin(0));
  }
}

Java 1.5 propose une solution pour réduire le code à écrire concernant les membres statiques en proposant une nouvelle fonctionnalité concernant l'importation de package : l'import statique (static import).

Ce nouveau concept permet d'appliquer les mêmes règles aux membres statiques qu'aux classes et interfaces pour l'importation classique.

Cette nouvelle fonctionnalité est développée dans la JSR 201. Elle s'utilise comme une importation classique en ajoutant le mot clé static.

Exemple (java 1.5) :
import static java.lang.Math.*;

public class TestStaticImport {

  public static void main(String[] args) {
    System.out.println(PI);
    System.out.println(sin(0));
  }
}

L'utilisation de l'importation statique s'applique à tous les membres statiques : constantes et méthodes statiques de l'élément importé.

 

8.4. Les annotations ou métadonnées (Meta Data)

Cette nouvelle fonctionnalité est spécifiée dans la JSR 175.

Elle propose de standardiser l'ajout d'annotations dans le code. Ces annotations pourront ensuite être traitées par des outils pour générer d'autres éléments tels que des fichiers de configuration ou du code source.

Ces annotations concernent les classes, les méthodes et les champs. La syntaxe des annotations utilise le caractère « @ ».

La mise en oeuvre détaillée des annotations est proposée dans le chapitre qui leur est consacré.

 

8.5. Les arguments variables (varargs)

Cette nouvelle fonctionnalité va permettre de passer un nombre non défini d'arguments d'un même type à une méthode. Ceci va éviter de devoir encapsuler ces données dans une collection.

Cette nouvelle fonctionnalité est spécifiée dans la JSR 201. Elle implique une nouvelle notation pour préciser la répétition d'un type d'argument. Cette nouvelle notation utilise trois petits points : ...

Exemple (java 1.5) :
public class TestVarargs {

  public static void main(String[] args) {
    System.out.println("valeur 1 = " + additionner(1,2,3));
    System.out.println("valeur 2 = " + additionner(2,5,6,8,10));
  }

  public static int additionner(int ... valeurs) {
    int total = 0;
  	
    for (int val : valeurs) {
      total += val;
  	}
  	
    return total;
  }
}

Résultat :
C:\tiger>java TestVarargs
valeur 1 = 6
valeur 2 = 31

L'utilisation de la notation ... permet le passage d'un nombre indéfini de paramètres du type précisé. Tous ces paramètres sont traités comme un tableau : il est d'ailleurs possible de fournir les valeurs sous la forme d'un tableau.

Exemple (java 1.5) :
public class TestVarargs2 {

  public static void main(String[] args) {
    int[] valeurs = {1,2,3,4};
    System.out.println("valeur 1 = " + additionner(valeurs));
  }

  public static int additionner(int ... valeurs) {
    int total = 0;
  	
  	for (int val : valeurs) {
      total += val;
  	}
  	
  	return total;
  }
}

Résultat :
C:\tiger>java TestVarargs2
valeur 1 = 10

Il n'est cependant pas possible de mixer des éléments unitaires et un tableau dans la liste des éléments fournis en paramètres.

Exemple (java 1.5) :
public class TestVarargs3 {

  public static void main(String[] args) {
    int[] valeurs = {1,2,3,4};
    System.out.println("valeur 1 = " + additionner(5,6,7,valeurs));
  }

  public static int additionner(int ... valeurs) {
    int total = 0;
  	
    for (int val : valeurs) {
      total += val;
    }
  	
    return total;
  }
}

Résultat :
C:\tiger>javac -source 1.5 -target 1.5 TestVarargs3.java
TestVarargs3.java:7: additionner(int[]) in TestVarargs3 cannot be applied to (in
t,int,int,int[])
    System.out.println("valeur 1 = " + additionner(5,6,7,valeurs));
                                       ^
1 error

 

8.6. Les generics

Les generics permettent d'accroître la lisibilité du code et surtout d'en renforcer la sécurité grâce à un typage plus exigeant. Ils permettent de préciser explicitement le type d'un objet et rendent le cast vers ce type implicite. Cette nouvelle fonctionnalité est spécifiée dans la JSR 14.

Ils permettent par exemple de spécifier quel type d'objets une collection peut contenir et ainsi éviter l'utilisation d'un cast pour obtenir un élément de la collection.

L'inconvénient majeur du cast est que celui-ci ne peut être vérifié qu'à l'exécution et qu'il peut échouer. Avec l'utilisation des generics, le compilateur pourra réaliser cette vérification lors de la phase de compilation : la sécurité du code est ainsi renforcée.

Exemple (java 1.5) :
import java.util.*;

public class TestGenericsOld {

  public static void main(String[] args) {

    List liste = new ArrayList();
    String valeur = null;
    for(int i = 0; i < 10; i++) {
      valeur = ""+i;
      liste.add(valeur);
    }
 
    for (Iterator iter = liste.iterator(); iter.hasNext(); ) {
      valeur = (String) iter.next();
      System.out.println(valeur.toUpperCase());
    }
  }
}

L'utilisation des generics va permettre au compilateur de faire la vérification au moment de la compilation et ainsi de s'assurer d'une exécution correcte. Ce mécanisme permet de s'assurer que les objets contenus dans la collection seront homogènes.

La syntaxe pour mettre en oeuvre les generics utilise les symboles < et > pour préciser le ou les types des objets à utiliser. Seuls des objets peuvent être utilisés avec les generics : si un type primitif est utilisé dans les generics, une erreur de type « unexpected type » est générée lors de la compilation.

Exemple (java 1.5) :
import java.util.*;

public class TestGenerics {

  public static void main(String[] args) {

    List<String> liste = new ArrayList();
    String valeur = null;
    for(int i = 0; i < 10; i++) {
      valeur = ""+i;
      liste.add(valeur);
    }
 
    for (Iterator<String> iter = liste.iterator(); iter.hasNext(); ) {
      System.out.println(iter.next().toUpperCase());
    }
  }
}

Si un objet de type différent de celui déclaré dans le generic est utilisé dans le code, le compilateur émet une erreur lors de la compilation.

Exemple (java 1.5) :
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

public class TestGenerics2 {

  public static void main(String[] args) {

    List<String> liste = new ArrayList();
    String valeur = null;
    for (int i = 0; i < 10; i++) {
      liste.add(valeur);
      liste.add(new Date());
    }

    for (Iterator<String> iter = liste.iterator(); iter.hasNext();) {
      System.out.println(iter.next().toUpperCase());
    }
  }
}

Résultat :
C:\tiger>javac -source 1.5 -target 1.5 TestGenerics2.java
TestGenerics2.java:14: error: no suitable method found for add(Date)
      liste.add(new Date());
           ^
    method List.add(int,String) is not applicable
      (actual and formal argument lists differ in length)
    method List.add(String) is not applicable
      (actual argument Date cannot be converted to String by method invocation c
onversion)
Note: TestGenerics2.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error

L'utilisation des generics permet de rendre le code plus lisible et plus sûr notamment car il n'est plus nécessaire d'utiliser un cast et de définir une variable intermédiaire.

Les generics peuvent être utilisés avec trois éléments :

  • Les classes
  • Les interfaces
  • Les méthodes

Pour définir une classe utilisant les generics, il suffit de déclarer leur utilisation dans la signature de la classe à l'aide des caractères < et >. Ce type de déclaration est appelé type paramétré (parameterized type). Dans ce cas, les paramètres fournis dans la déclaration du generic sont des variables de types. Si la déclaration possède plusieurs variables de type alors il faut les séparer par un caractère virgule.

Exemple (java 1.5) :
public class MaClasseGeneric<T1, T2> {
  private T1 param1;
  private T2 param2;
  
  public MaClasseGeneric(T1 param1, T2 param2) {
    this.param1 = param1;
    this.param2 = param2;
  }

  public T1 getParam1() {
    return this.param1;
  }

  public T2 getParam2() {
    return this.param2;
  }	
}

Lors de l'utilisation de la classe, il faut utiliser les types paramétrés pour indiquer le type des objets à utiliser.

Exemple (java 1.5) :
import java.util.*;

public class TestClasseGeneric {

  public static void main(String[] args) {
    MaClasseGeneric<Integer, String> maClasse = 
	  new MaClasseGeneric<Integer, String>(1, "valeur 1");
    Integer param1 = maClasse.getParam1();
    String  param2 = maClasse.getParam2();
  }
}

Le principe est identique avec les interfaces.

La syntaxe utilisant les caractères < et > se situe toujours après l'entité qu'elle concerne.

Exemple (java 1.5) :
    MaClasseGeneric<Integer, String> maClasse = 
	        new MaClasseGeneric<Integer, String>(1, "valeur 1");
    MaClasseGeneric<Integer, String>[] maClasses;

Même le cast peut être utilisé avec le generic en utilisant le nom du type paramétré dans le cast.

Il est possible de préciser une relation entre une variable de type et une classe ou interface : ainsi, avec le mot clé extends dans la variable de type, il sera possible d'utiliser une instance du type paramétré avec n'importe quel objet qui hérite ou implémente la classe ou l'interface précisée.

Exemple (java 1.5) :
import java.util.*;

public class MaClasseGeneric2<T1 extends Collection> {
  private T1 param1;
  
  public MaClasseGeneric2(T1 param1) {
    this.param1 = param1;
  }

  public T1 getParam1() {
    return this.param1;
  }
}

L'utilisation du type paramétré MaClasseGeneric2 peut être réalisée avec n'importe quelle classe qui hérite de l'interface java.util.Collection.

Exemple (java 1.5) :
import java.util.*;

public class TestClasseGeneric2 {

  public static void main(String[] args) {
    MaClasseGeneric2<ArrayList> maClasseA = 
	  new MaClasseGeneric2<ArrayList>(new ArrayList());
    MaClasseGeneric2<TreeSet> maClasseB = 
	  new MaClasseGeneric2<TreeSet>(new TreeSet());
  }
}

Ce mécanisme permet une utilisation un peu moins strict du typage dans les generics.

L'utilisation d'une classe qui n'hérite pas de la classe ou n'implémente pas l'interface définie dans la variable de type, provoque une erreur à la compilation.

Exemple (java 1.5) :
C:\tiger>javac -source 1.5 -target 1.5 TestClasseGeneric2.java
TestClasseGeneric2.java:8: type parameter java.lang.String is not within its bou
nd
    MaClasseGeneric2<String> maClasseC = new MaClasseGeneric2<String>("test");
                     ^
TestClasseGeneric2.java:8: type parameter java.lang.String is not within its bou
nd
    MaClasseGeneric2<String> maClasseC = new MaClasseGeneric2<String>("test");
                                                              ^
2 errors

 

8.7. Les boucles pour le parcours des collections

L'itération sur les éléments d'une collection est fastidieuse avec la déclaration d'un objet de type Iterator.

Exemple :
import java.util.*;

public class TestForOld {

  public static void main(String[] args) {
    List liste = new ArrayList();
    for(int i = 0; i < 10; i++) {
      liste.add(i);
    }
    
    for (Iterator iter = liste.iterator(); iter.hasNext(); ) {
      System.out.println(iter.next());
    }
  }
}

La nouvelle forme de l'instruction for, spécifiée dans la JSR 201, permet de simplifier l'écriture du code pour réaliser une telle itération et laisse le soin au compilateur de générer le code nécessaire.

Exemple (java 1.5) :
import java.util.*;

public class TestFor {

  public static void main(String[] args) {
    List liste = new ArrayList();
    for(int i = 0; i < 10; i++) {
      liste.add(i);
    }
    
    for (Object element : liste) {
      System.out.println(element);
    }
  }
}

L'utilisation de la nouvelle syntaxe de l'instruction for peut être renforcée en combinaison avec les generics, ce qui évite l'utilisation d'un cast.

Exemple (java 1.5) :
import java.util.*;
import java.text.*;

public class TestForGenerics {

  public static void main(String[] args) {
    List<Date> liste = new ArrayList();
    for(int i = 0; i < 10; i++) {
      liste.add(new Date());
    }
    
    DateFormat df = DateFormat.getDateInstance();

    for (Date element : liste) {
      System.out.println(df.format(element));
    }
  }
}

La nouvelle syntaxe de l'instruction peut aussi être utilisée pour parcourir tous les éléments d'un tableau.

Exemple (java 1.5) :
import java.util.*;

public class TestForArray {

  public static void main(String[] args) {
    int[] tableau = {0,1,2,3,4,5,6,7,8,9};
    
    for (int element : tableau) {
      System.out.println(element);
    }
  }
}

L'exemple précédent fait aussi usage d'une autre nouvelle fonctionnalité du JDK 1.5 : l'unboxing.

Cela permet d'éviter la déclaration et la gestion dans le code d'une variable contenant l'index courant lors du parcours du tableau.

 

8.8. Les énumérations (type enum)

Souvent lors de l'écriture de code, il est utile de pouvoir définir un ensemble fini de valeurs d'une donnée ; par exemple, pour définir les valeurs possibles qui vont caractériser l'état de cette donnée.

Pour cela, le type énumération permet de définir un ensemble de constantes : une énumération est un ensemble fini d'éléments constants. Cette fonctionnalité existe déjà dans les langages C et Delphi, entre autres.

Jusqu'à la version 1.4 incluse, la façon la plus pratique pour palier le manque du type enum était de créer des constantes dans une classe.

Exemple :
public class FeuTricolore {
  public static final int VERT = 0;
  public static final int ORANGE = 1;
  public static final int ROUGE = 2;
}

Cette approche fonctionne : les constantes peuvent être sérialisées et utilisées dans une instruction switch mais leur mise en oeuvre n'est pas type safe. Rien n'empêche d'affecter une autre valeur à la donnée de type int qui va stocker une des valeurs constantes.

A défaut, cette solution permet de répondre au besoin mais elle présente cependant quelques inconvénients :

  • le principal inconvénient de cette technique est qu'il n'y a pas de contrôle sur la valeur affectée à une donnée surtout si les constantes ne sont pas utilisées : il est possible d'utiliser n'importe quelle valeur permise par le type de la variable en plus des constantes définies. Le compilateur ne peut faire aucun contrôle sur les valeurs utilisées
  • il n'est pas possible de faire une itération sur chacune des valeurs
  • il n'est pas possible d'associer des traitements à une constante
  • les modifications faites dans ces constantes notamment les changements de valeurs ne sont pas automatiquement reportées dans les autres classes qui doivent être explicitement recompilées

Java 5 apporte un nouveau type nommé enum qui permet de définir un ensemble de champs constants. Cette nouvelle fonctionnalité est spécifiée dans la JSR 201.

Un exemple classique est l'énumération des jours de la semaine.

Exemple :
public enum Jour {
  LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI, SAMEDI, DIMANCHE
}

Les énumérations permettent de définir un ensemble fini de constantes, chacune d'entre-elles est séparée des autres par une virgule. Comme ces champs sont constants, leur nom est en majuscule par convention.

 

8.8.1. La définition d'une énumération

La définition d'une énumération ressemble à celle d'une classe avec quelques différences :

  • utilisation du mot clé enum introduit spécifiquement dans ce but à la place du mot clé class
  • un ensemble de valeurs constantes définies au début du corps de la définition, chaque valeur étant séparée par une virgule
  • par convention le nom des constantes est en majuscule

Une énumération peut prendre plusieurs formes et être enrichie de fonctionnalités puisqu'une énumération est une classe Java.

Dans sa forme la plus simple, la déclaration d'une énumération se résume à définir l'ensemble des constantes.

Exemple :
public enum FeuTricolore {
  VERT, ORANGE, ROUGE
};

Les énumérations peuvent être déclarées à plusieurs niveaux. Le mot clé enum est au même niveau que le mot clé class ou interface : une énumération peut donc être déclarée au même endroit qu'une classe ou une interface, que cela soit dans un fichier dédié ou dans le fichier d'une autre classe.

Exemple :
public class TestEnum2 {
  public enum MonStyle {
    STYLE_1, STYLE_2, STYLE_3, STYLE_4, STYLE_5
  };

  public static void main(String[] args) {
    afficher(MonStyle.STYLE_2);
  }

  public static void afficher(MonStyle style) {
    System.out.println(style);
  }
}

Lors de la compilation de cet exemple, une classe interne est créée pour encapsuler l'énumération.

Résultat :
C:\java\workspace\TestEnum\bin>dir
 Le volume dans le lecteur C s'appelle Disque_C
 Le numéro de série du volume est 043F-2ED6

 Répertoire de C:\java\workspace\TestEnum\bin

15/07/2010  16:33    <REP>          .
15/07/2010  16:33    <REP>          ..
15/07/2010  16:39             1 160 TestEnum2$MonStyle.class
15/07/2010  16:39               743 TestEnum2.class
               2 fichier(s)            1 903 octets
               2 Rép(s)  23 175 589 888 octets libres

Les modificateurs d'accès s'appliquent à une énumération.

L'outil Javadoc recense les énumérations dans le fichier package-summary.phpl.

 

8.8.2. L'utilisation d'une énumération

Le nom utilisé dans la déclaration de l'énumération peut être utilisé comme n'importe quelle classe dans la déclaration d'un type.

Une fois définie, il est possible d'utiliser l'énumération simplement en définissant une variable du type de l'énumération.

Exemple :
public class TestEnum {
  Jour jour;
  
  public TestEnum(Jour jour) {
    this.jour = jour;
  }

  public void afficherJour() {
    switch (jour) {
    case LUNDI:   
      System.out.println("Lundi");
      break;
    case MARDI:
      System.out.println("Mardi");
      break;
    case MERCREDI:
      System.out.println("Mercredi");
      break;
    case JEUDI:
      System.out.println("Jeudi");
      break;
    case VENDREDI:
      System.out.println("Vendredi");
      break;
    case SAMEDI:
      System.out.println("Samedi");
      break;
    case DIMANCHE:
      System.out.println("Dimanche");
      break;
    }
  }
  
  public static void main(String[] args) {
    TestEnum testEnum = new TestEnum(Jour.SAMEDI);
    testEnum.afficherJour();
  }
}

Lors de l'utilisation d'une constante, son nom doit être préfixé par le nom de l'énumération sauf dans le cas de l'utilisation dans une instruction switch.

Les énumérations étant transformées en une classe par le compilateur, ce dernier effectue une vérification de type lors de l'utilisation de l'énumération..

L'instruction switch a été modifiée pour permettre de l'utiliser avec une énumération puisque bien qu'étant physiquement une classe, celle-ci possède une liste finie de valeurs associées.

L'utilisation d'une énumération dans l'instruction switch impose de n'utiliser que le nom de la valeur sans la préfixer par le nom de l'énumération sinon une erreur est émise par le compilateur.

Exemple :
    switch(feu) {
    case (FeuTricolore.VERT) :
      System.out.println("passer");
      break;
    default :
      System.out.println("arreter");
      break;
    }

Résultat :
Feu.java:24: an enum switch case label must be the unqualified name of an enumeration constant

Chaque élément d'une énumération est associé à une valeur par défaut, qui débute à zéro et qui est incrémentée de un en un. La méthode ordinal() permet d'obtenir cette valeur.

Exemple :
    FeuTricolore feu = FeuTricolore.VERT;
    System.out.println(feu.ordinal());

Il y a plusieurs avantages à utiliser les enums à la place des constantes notamment le typage fort et le préfixe de l'élément par le nom de l'énumération.

 

8.8.3. L'enrichissement de l'énumération

Une énumération peut mettre en oeuvre la plupart des fonctionnalités et des comportements d'une classe :

  • implémenter une ou plusieurs interfaces
  • avoir plusieurs constructeurs
  • avoir des champs et des méthodes
  • avoir un bloc d'initialisation statique
  • avoir des classes internes (inner classes)

Un type enum hérite implicitement de la classe java.lang.Enum : il ne peut donc pas hériter d'une autre classe.

Exemple :
public enum MonEnum extends Object {
  UN, DEUX, TROIS;
}

Résultat :
MyType.java:3: '{' expected
public enum MonEnum extends Object {
MonEnum.java:6: expected
2 errors

Chacun des éléments de l'énumération est instancié par le constructeur sous la forme d'un champ public static.

Si les éléments de l'énumération sont définis sans argument alors un constructeur sans argument doit être proposé dans la définition de l'énumération (celui-ci peut être le constructeur par défaut si aucun autre constructeur n'est défini).

Le fait qu'une énumération soit une classe permet de définir un espace de nommage pour ses éléments ce qui évite les collisions, par exemple Puissance.ELEVEE et Duree.ELEVEE.

A partir du code source de l'énumération, le compilateur va générer une classe enrichie avec certaines fonctionnalités.

Exemple :
C:\Users\Jean Michel\workspace\TestEnum\bin>javap FeuTricolore
Compiled from "FeuTricolore.java"
public final class FeuTricolore extends java.lang.Enum{
    public static final FeuTricolore VERT;
    public static final FeuTricolore ORANGE;
    public static final FeuTricolore ROUGE;
    static {};
    public static FeuTricolore[] values();
    public static FeuTricolore valueOf(java.lang.String);
}

La classe compilée a été enrichie automatiquement par le compilateur qui a identifié l'entité comme une énumération grâce au mot clé enum :

  • la classe compilée hérite de la classe java.lang.Enum.
  • un champ public static final du type de l'énumération est ajouté pour chaque élément
  • un bloc d'initialisation static permet de créer les différentes instances statiques des éléments
  • les méthodes valueOf() et values() sont ajoutées

Le compilateur ajoute automatiquement certaines méthodes à une classe de type enum lors de la compilation, notamment les méthodes statiques :

  • values() qui renvoie un tableau des valeurs de l'énumération
  • valueOf() qui renvoie l'élément de l'énumération dont le nom est fourni en paramètre

Le nom fourni en paramètre de la méthode valueOf() doit correspondre exactement à l'identifiant utilisé dans la déclaration de l'énumération. Il n'est pas possible de redéfinir la méthode valueOf().

Une énumération propose une implémentation par défaut de la méthode toString() : par défaut, elle renvoie le nom de la constante. Il est possible de la redéfinir au besoin.

Il est possible de préciser une valeur pour chaque élément de l'énumération lors de sa définition : celle-ci sera alors stockée et pourra être utilisée dans les traitements.

Exemple :
public enum Coefficient {
  UN(1), DEUX(2), QUATRE(4);

  private final int valeur;

  private Coefficient(int valeur) {
    this.valeur = valeur;
  }

  public int getValeur() {
    return this.valeur;
  }
}

Dans ce cas, l'énumération doit implicitement définir :

  • un attribut qui contient la valeur associée à l'élément
  • un constructeur qui attend en paramètre la valeur
  • une méthode de type getter pour obtenir la valeur

Attention : toutes les données manipulées dans un élément d'une énumération doivent être immuables. Par exemple, il ne faut pas encapsuler dans un élément d'une énumération une donnée dont la valeur peut fluctuer dans le temps puisque l'élément est instancié une seule et unique fois.

Il faut obligatoirement définir les constantes en premier, avant toute définition de champs ou de méthodes. Si l'enum contient des champs et/ou des méthodes, il est impératif de terminer la définition des contantes par un point virgule.

Il est aussi possible de fournir plusieurs valeurs à un élément de l'énumération : comme une énumération est une classe, il est possible d'associer plusieurs valeurs à un élément de l'énumération. Ces valeurs seront stockées sous la forme de propriétés et l'énumération doit fournir un constructeur qui doit accepter en paramètre les valeurs de chaque propriété.

Exemple :
import java.math.BigDecimal;

public enum Remise {

  COURANTE(new BigDecimal("0.05"), "Remise de 5%"), 
  FIDELITE(new BigDecimal("0.07"), "Remise de 7%"),
  EXCEPTIONNELLE(new BigDecimal("0.10"), "Remise de 10%");

  private final BigDecimal taux;
  private final String libelle;

  private Remise(BigDecimal taux, String libelle) {
    this.taux = taux;
    this.libelle = libelle;
  }

  public BigDecimal getTaux() {
    return this.taux;
  }

  public String getLibelle() {
    return this.libelle;
  }

  public BigDecimal calculer(BigDecimal valeur) {
    return valeur.multiply(taux).setScale(2, BigDecimal.ROUND_FLOOR);
  }

  public static void main(String[] args) {
    BigDecimal montant = new BigDecimal("153.99");

    for (Remise remise : Remise.values()) {
      System.out.println(remise.getLibelle() + " \t" 
        + remise.calculer(montant));
    }
  }
}

Résultat :
Remise de 5%   7.69
Remise de 7%   10.77
Remise de 10%  15.39

Dans l'exemple précédent, chaque constante est définie avec les deux paramètres qui la compose : le taux et le libellé. Ces valeurs sont passées au constructeur par le bloc d'initialisation static qui est créé par le compilateur.

Le constructeur d'une classe de type enum ne peut pas être public car il ne doit être invoqué que par la classe elle-même pour créer les constantes définies au début de la déclaration de l'énumération.

Un élément d'une énumération ne peut avoir que la valeur avec laquelle il est défini dans sa déclaration. Ceci justifie que le constructeur ne soit pas public et qu'une énumération ne puisse pas avoir de classes filles.

Tous les éléments de l'énumération sont encapsulés dans une instance finale du type de l'énumération : ce sont donc des singletons. De plus, les valeurs peuvent être testées avec l'opérateur == puisqu'elles sont déclarées avec le modificateur final.

Plusieurs fonctionnalités permettent de s'assurer qu'il n'y aura pas d'autres instances que celles définies par le compilateur à partir du code source :

  • il n'y a pas de constructeur public qui permette de l'invoquer pour créer une nouvelle instance
  • il n'est pas possible d'hériter d'une autre classe que la classe Enum
  • il n'est pas possible de créer une classe fille de l'énumération puisqu'elle est déclarée finale
  • l'invocation de la méthode clone() de l'énumération lève une exception de type CloneNotSupportedException

Une énumération peut implémenter une ou plusieurs interfaces. Comme une énumération est une classe, elle peut aussi contenir une méthode main().

 

8.8.4. La personnalisation de chaque élément

Le type Enum de Java est plus qu'une simple liste de constantes car une énumeration est définie dans une classe. Une classe de type Enum peut donc contenir des champs et des méthodes dédiées.

Pour encore plus de souplesse, il est possible de définir chaque élément sous la forme d'une classe interne dans laquelle on fournit une implémentation particulière pour chaque élément.

Il est ainsi possible de définir explicitement, pour chaque valeur, le corps de la classe qui va l'encapsuler. Une telle définition est similaire à la déclaration d'une classe anonyme. Cette classe est implicitement une extension de la classe englobante. Il est ainsi possible de redéfinir une méthode de l'énumération.

Exemple :
import java.math.BigDecimal;

public enum Remise {

  COURANTE(new BigDecimal("0.05"), "Remise de 5%") {
    @Override
    public String toString() {
      return "Remise 5%";
    }
  },
  FIDELITE(new BigDecimal("0.07"), "Remise de 7%") {
    @Override
    public String toString() {
      return "Remise fidelite 7%";
    }
  },
  EXCEPTIONNELLE(new BigDecimal("0.10"), "Remise de 10%") {
    @Override
    public String toString() {
      return "Remise exceptionnelle 10%";
    }

    @Override
    public String getLibelle() {
      return "Remise à titre exceptionnel de 10%";
    }
  };

  private final BigDecimal taux;
  private final String libelle;

  private Remise(BigDecimal taux, String libelle) {
    this.taux = taux;
    this.libelle = libelle;
  }

  public BigDecimal getTaux() {
    return this.taux;
  }

  public String getLibelle() {
    return this.libelle;
  }

  public BigDecimal calculer(BigDecimal valeur) {
    return valeur.multiply(taux).setScale(2, BigDecimal.ROUND_FLOOR);
  }

  public static void main(String[] args) {
    BigDecimal montant = new BigDecimal("153.99");

    for (Remise remise : Remise.values()) {
      System.out.println(remise + " \t" + remise.calculer(montant));
    }
  }
}

Résultat :
Remise 5%      7.69
Remise fidelite 7%     10.77
Remise exceptionnelle 10%      15.39

Il est aussi possible de définir une méthode abstract dans l'énumération pour forcer la définition de la méthode dans chaque élément.

Exemple :
import java.math.BigDecimal;

public enum Remise {

  COURANTE(new BigDecimal("0.05"), "Remise de 5%") {

    @Override
    public String getLibelle() {
      return this.libelle;
    }

  },
  FIDELITE(new BigDecimal("0.07"), "Remise de 7%") {

    @Override
    public String getLibelle() {
      return "Remise fidélité de 7%";
    }
  },
  EXCEPTIONNELLE(new BigDecimal("0.10"), "Remise de 10%") {

    @Override
    public String getLibelle() {
      return "Remise à titre exceptionnel de 10%";
    }
  };

  private final BigDecimal taux;
  protected final String libelle;

  private Remise(BigDecimal taux, String libelle) {
    this.taux = taux;
    this.libelle = libelle;
  }

  public BigDecimal getTaux() {
    return this.taux;
  }

  public abstract String getLibelle();

  public BigDecimal calculer(BigDecimal valeur) {
    return valeur.multiply(taux).setScale(2, BigDecimal.ROUND_FLOOR);
  }

  public static void main(String[] args) {
    BigDecimal montant = new BigDecimal("153.99");

    for (Remise remise : Remise.values()) {
      System.out.println(remise.getLibelle() + " \t" + remise.calculer(montant));
    }
  }
}

Résultat :
Remise de 5%   7.69
Remise fidélité de 7%  10.77
Remise à titre exceptionnelle de 10%  15.39

Il faut cependant utiliser cette possibilité avec parcimonie car le code est moins lisible.

 

8.8.5. Les limitations dans la mise en oeuvre des énumérations

La mise en oeuvre des énumérations présente plusieurs limitations.

L'ordre de définition du contenu de l'énumération est important : les éléments de l'énumération doivent être définis en premier.

Un élément d'une énumération ne doit pas être null.

Un type Enum hérite implicitement de la classe java.lang.Enum : il ne peut pas hériter d'une autre classe mère.

Pour garantir qu'il n'y ait qu'une seule instance d'un élément d'une énumération, le constructeur n'est pas accessible et l'énumération ne peut pas avoir de classe fille.

Une énumération ne peut pas être définie localement dans une méthode.

La méthode values() renvoie un tableau des éléments de l'énumération dans l'ordre dans lequel ils sont déclarés mais il ne faut surtout pas utiliser l'ordre des éléments d'une énumération dans les traitements : il ne faut par exemple pas tester la valeur retournée par la méthode ordinal() dans les traitements. Des problèmes apparaitront à l'exécution si l'ordre des éléments est modifié car le compilateur ne peut pas détecter ce type de changement.

Il n'est pas possible de personnaliser la sérialisation d'une énumération en redéfinissant les méthodes writeObject() et writeReplace() qui seront ignorées lors de la sérialisation. De plus, la déclaration d'un serialVersionUlD est ignorée car sa valeur est toujours 0L.

 


Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]