Comment utiliser les énumérations typesafe en Java

Le code Java qui utilise des types énumérés traditionnels pose problème. Java 5 nous a donné une meilleure alternative sous la forme d'énumérations de type sécurisé. Dans cet article, je vous présente les types énumérés et les énumérations typesafe, je vous montre comment déclarer une énumération typesafe et l'utiliser dans une instruction switch, et je discute de la personnalisation d'une énumération typesafe en ajoutant des données et des comportements. Je termine l'article en explorant la classe.java.lang.Enum

télécharger Obtenir le code Téléchargez le code source pour des exemples dans ce didacticiel Java 101. Créé par Jeff Friesen pour JavaWorld /.

Des types énumérés aux énumérations sécurisées

Un type énuméré spécifie un ensemble de constantes associées comme ses valeurs. Les exemples incluent une semaine de jours, les directions standard de la boussole nord / sud / est / ouest, les dénominations de pièces d'une monnaie et les types de jetons d'un analyseur lexical.

Les types énumérés ont traditionnellement été implémentés sous forme de séquences de constantes entières, ce qui est démontré par l'ensemble suivant de constantes de direction:

statique final int DIR_NORTH = 0; static final int DIR_WEST = 1; statique final int DIR_EAST = 2; statique final int DIR_SOUTH = 3;

Il y a plusieurs problèmes avec cette approche:

  • Manque de sécurité de type: comme une constante de type énuméré n'est qu'un entier, n'importe quel entier peut être spécifié là où la constante est requise. De plus, l'addition, la soustraction et d'autres opérations mathématiques peuvent être effectuées sur ces constantes; par exemple, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), ce qui n'a pas de sens.
  • Espace de noms non présent: les constantes d'un type énuméré doivent être préfixées avec une sorte d'identificateur unique (espérons-le) (par exemple DIR_) pour éviter les collisions avec les constantes d'un autre type énuméré.
  • Fragilité: étant donné que les constantes de type énumérées sont compilées dans des fichiers de classe où leurs valeurs littérales sont stockées (dans des pools de constantes), la modification de la valeur d'une constante nécessite que ces fichiers de classe et les fichiers de classe d'application qui en dépendent soient reconstruits. Sinon, un comportement non défini se produira lors de l'exécution.
  • Manque d'informations: lorsqu'une constante est imprimée, sa valeur entière est sortie. Cette sortie ne vous dit rien sur ce que représente la valeur entière. Il n'identifie même pas le type énuméré auquel appartient la constante.

Vous pouvez éviter les problèmes de «manque de sécurité de type» et de «manque d'information» en utilisant des java.lang.Stringconstantes. Par exemple, vous pouvez spécifier static final String DIR_NORTH = "NORTH";. Bien que la valeur de la constante soit plus significative, les Stringconstantes basées sur la base souffrent toujours de problèmes d '«espace de noms non présent» et de fragilité. En outre, contrairement aux comparaisons d'entiers, vous ne pouvez pas comparer les valeurs de chaîne avec les opérateurs ==et !=(qui comparent uniquement les références).

Ces problèmes ont amené les développeurs à inventer une alternative basée sur les classes appelée Typesafe Enum . Ce modèle a été largement décrit et critiqué. Joshua Bloch a présenté le modèle dans le point 21 de son Guide du langage de programmation Java efficace (Addison-Wesley, 2001) et a noté qu'il avait quelques problèmes; à savoir qu'il est difficile d'agréger les constantes d'énumération typesafe en ensembles, et que les constantes d'énumération ne peuvent pas être utilisées dans les switchinstructions.

Prenons l'exemple suivant du modèle d'énumération typesafe. La Suitclasse montre comment vous pouvez utiliser l'alternative basée sur la classe pour introduire un type énuméré qui décrit les quatre combinaisons de cartes (clubs, diamants, cœurs et piques):

public final class Suit // Ne devrait pas pouvoir sous-classer Suit. {public static final Suit CLUBS = new Suit (); public static final Suit DIAMONDS = new Suit (); public static final Suit HEARTS = new Suit (); public static final Suit SPADES = new Suit (); private Suit () {} // Ne devrait pas pouvoir introduire de constantes supplémentaires. }

Pour utiliser cette classe, vous devez introduire une Suitvariable et l'affecter à l'une des Suitconstantes de, comme suit:

Costume costume = Suit.DIAMONDS;

Vous pourriez alors vouloir interroger suitdans une switchdéclaration comme celle-ci:

switch (suit) {case Suit.CLUBS: System.out.println ("clubs"); Pause; case Suit.DIAMONDS: System.out.println ("diamants"); Pause; case Suit.HEARTS: System.out.println ("coeurs"); Pause; case Suit.SPADES: System.out.println ("pique"); }

Cependant, lorsque le compilateur Java rencontre Suit.CLUBS, il signale une erreur indiquant qu'une expression constante est requise. Vous pouvez essayer de résoudre le problème comme suit:

switch (suit) {case CLUBS: System.out.println ("clubs"); Pause; case DIAMONDS: System.out.println ("diamants"); Pause; case HEARTS: System.out.println ("coeurs"); Pause; case SPADES: System.out.println ("pique"); }

Cependant, lorsque le compilateur rencontre CLUBS, il signale une erreur indiquant qu'il n'a pas pu trouver le symbole. Et même si vous avez placé Suitdans un package, importé le package et importé statiquement ces constantes, le compilateur se plaindrait de ne pas pouvoir convertir Suiten intlors de la rencontre suitavec switch(suit). Concernant chacun case, le compilateur signalera également qu'une expression constante est requise.

Java ne prend pas en charge le modèle Typesafe Enum avec des switchinstructions. Cependant, il a introduit la fonctionnalité de langage enum typesafe pour encapsuler les avantages du modèle tout en résolvant ses problèmes, et cette fonctionnalité prend en charge switch.

Déclarer une énumération de type sécurisé et l'utiliser dans une instruction switch

Une simple déclaration d'énumération de type sécurisé dans le code Java ressemble à ses équivalents dans les langages C, C ++ et C #:

enum Direction {NORD, OUEST, EST, SUD}

Cette déclaration utilise le mot-clé enumpour introduire Directioncomme une énumération de type sécurisé (un type spécial de classe), dans laquelle des méthodes arbitraires peuvent être ajoutées et des interfaces arbitraires peuvent être implémentées. Le NORTH, WEST, EAST, et les SOUTHconstantes enum sont mises en oeuvre en tant que corps de classes spécifiques de constantes qui définissent les classes anonymes étendant la enfermant Directionclasse.

Directionet d' autres énumérations Typesafe étendent  et hériteront diverses méthodes, y compris , et , de cette classe. Nous explorerons plus tard dans cet article.Enum values()toString()compareTo()Enum

Le listing 1 déclare l'énumération susmentionnée et l'utilise dans une switchinstruction. Il montre également comment comparer deux constantes d'énumération, pour déterminer quelle constante vient avant l'autre constante.

Liste 1: TEDemo.java(version 1)

public class TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static void main (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .values ​​() [i]; System.out.println (d); switch (d) {case NORTH: System.out.println ("Déplacer vers le nord"); Pause; case WEST: System.out.println ("Déplacer vers l'ouest"); Pause; case EAST: System.out.println ("Déplacer vers l'est"); Pause; case SOUTH: System.out.println ("Déplacer vers le sud"); Pause; par défaut: assert false: "direction inconnue"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Le listing 1 déclare l' Directionénumération typesafe et itère sur ses membres constants, ce qui values()retourne. Pour chaque valeur, l' switchinstruction (améliorée pour prendre en charge les énumérations typesafe) choisit le casequi correspond à la valeur de  d et génère un message approprié. (Vous ne préfixez pas une constante enum, par exemple, NORTHavec son type enum.) Enfin, le Listing 1 évalue Direction.NORTH.compareTo(Direction.SOUTH)pour déterminer si NORTHvient avant SOUTH.

Compilez le code source comme suit:

javac TEDemo.java

Exécutez l'application compilée comme suit:

java TEDemo

Vous devez observer la sortie suivante:

NORD Déplacer vers le nord OUEST Déplacer vers l'ouest EST Déplacer vers l'est SUD Déplacer vers le sud -3

The output reveals that the inherited toString() method returns the name of the enum constant, and that NORTH comes before SOUTH in a comparison of these enum constants.

Adding data and behaviors to a typesafe enum

You can add data (in the form of fields) and behaviors (in the form of methods) to a typesafe enum. For example, suppose you need to introduce an enum for Canadian coins, and that this class must provide the means to return the number of nickels, dimes, quarters, or dollars contained in an arbitrary number of pennies. Listing 2 shows you how to accomplish this task.

Listing 2: TEDemo.java (version 2)

enum Coin { NICKEL(5), // constants must appear first DIME(10), QUARTER(25), DOLLAR(100); // the semicolon is required private final int valueInPennies; Coin(int valueInPennies) { this.valueInPennies = valueInPennies; } int toCoins(int pennies) { return pennies / valueInPennies; } } public class TEDemo { public static void main(String[] args) { if (args.length != 1) { System.err.println("usage: java TEDemo amountInPennies"); return; } int pennies = Integer.parseInt(args[0]); for (int i = 0; i < Coin.values().length; i++) System.out.println(pennies + " pennies contains " + Coin.values()[i].toCoins(pennies) + " " + Coin.values()[i].toString().toLowerCase() + "s"); } }

Listing 2 first declares a Coin enum. A list of parameterized constants identifies four kinds of coins. The argument passed to each constant represents the number of pennies that the coin represents.

The argument passed to each constant is actually passed to the Coin(int valueInPennies) constructor, which saves the argument in the valuesInPennies instance field. This variable is accessed from within the toCoins() instance method. It divides into the number of pennies passed to toCoin()’s pennies parameter, and this method returns the result, which happens to be the number of coins in the monetary denomination described by the Coin constant.

At this point, you’ve discovered that you can declare instance fields, constructors, and instance methods in a typesafe enum. After all, a typesafe enum is essentially a special kind of Java class.

The TEDemo class’s main() method first verifies that a single command-line argument has been specified. This argument is converted to an integer by calling the java.lang.Integer class’s parseInt() method, which parses the value of its string argument into an integer (or throws an exception when invalid input is detected). I’ll have more to say about Integer and its cousin classes in a future Java 101 article.

Moving forward, main() iterates over Coin’s constants. Because these constants are stored in a Coin[] array, main() evaluates Coin.values().length to determine the length of this array. For each iteration of loop index i, main() evaluates Coin.values()[i] to access the Coin constant. It invokes each of toCoins() and toString() on this constant, which further proves that Coin is a special kind of class.

Compile the source code as follows:

javac TEDemo.java

Run the compiled application as follows:

java TEDemo 198

You should observe the following output:

198 pennies contains 39 nickels 198 pennies contains 19 dimes 198 pennies contains 7 quarters 198 pennies contains 1 dollars

Exploring the Enum class

The Java compiler considers enum to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum class, which serves as the base class for all typesafe enums.

Enum’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum, you would interpret this formal type parameter list as follows:

  • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum.
  • A subclass of Enum (such as Coin) must follow the idiom that it supplies its own name (Coin) as an actual type argument.

Examine Enum’s Java documentation and you’ll discover that it overrides java.lang.Object's clone(), equals(), finalize(), hashCode(), and toString() methods. Except for toString(), all of these overriding methods are declared final so that they cannot be overridden in a subclass:

  • clone() is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via == and !=.
  • equals()est remplacé pour comparer les constantes via leurs références. Les constantes avec les mêmes identités ( ==) doivent avoir le même contenu ( equals()), et des identités différentes impliquent des contenus différents.
  • finalize() est remplacé pour garantir que les constantes ne peuvent pas être finalisées.
  • hashCode()est remplacé car il equals()est remplacé.
  • toString() est remplacé pour renvoyer le nom de la constante.

Enumfournit également ses propres méthodes. Ces méthodes comprennent les finalcompareTo() ( Enumoutils l' java.lang.Comparableinterface), getDeclaringClass(), name()et ordinal()méthodes: