Association de valeurs à Java Enum

1. Introduction

Le type enum Java fournit un moyen pris en charge par le langage pour créer et utiliser des valeurs constantes. En définissant un ensemble fini de valeurs, l' énumération est plus sûre de type que les variables littérales constantes telles que String ou int .

Cependant, les valeurs d' énumération doivent être des identificateurs valides et nous sommes encouragés à utiliser SCREAMING_SNAKE_CASE par convention.

Compte tenu de ces limitations, la valeur enum seule ne convient pas aux chaînes lisibles par l'homme ou aux valeurs non-chaîne .

Dans ce didacticiel, nous utiliserons les fonctionnalités de l' énumération comme classe Java pour attacher les valeurs souhaitées.

2. Utilisation de Java Enum comme classe

Nous créons souvent une énumération sous la forme d'une simple liste de valeurs. Par exemple, voici les deux premières lignes du tableau périodique sous forme d' énumération simple :

public enum Element { H, HE, LI, BE, B, C, N, O, F, NE }

En utilisant la syntaxe ci-dessus, nous avons créé dix instances finales statiques de l' énumération nommée Element . Bien que cela soit très efficace, nous n'avons capturé que les symboles des éléments. Et bien que la forme majuscule soit appropriée pour les constantes Java, ce n'est pas ainsi que nous écrivons normalement les symboles.

De plus, il nous manque également d'autres propriétés des éléments du tableau périodique, comme le nom et le poids atomique.

Bien que le type enum ait un comportement spécial en Java, nous pouvons ajouter des constructeurs, des champs et des méthodes comme nous le faisons avec d'autres classes. Pour cette raison, nous pouvons améliorer notre énumération pour inclure les valeurs dont nous avons besoin.

3. Ajout d'un constructeur et d'un champ final

Commençons par ajouter les noms des éléments. Nous allons définir les noms dans une variable finale à l'aide d'un constructeur :

public enum Element { H("Hydrogen"), HE("Helium"), // ... NE("Neon"); public final String label; private Element(String label) { this.label = label; } }

Tout d'abord, nous remarquons la syntaxe spéciale dans la liste des déclarations. C'est ainsi qu'un constructeur est appelé pour les types enum . Bien qu'il soit illégal d'utiliser l' opérateur new pour une énumération , nous pouvons passer des arguments de constructeur dans la liste de déclarations.

Nous déclarons ensuite une étiquette de variable d'instance . Il y a quelques points à noter à ce sujet.

Tout d'abord, nous avons choisi l' identifiant d' étiquette au lieu du nom . Bien que le nom du champ membre soit disponible, choisissons label pour éviter toute confusion avec la méthode prédéfinie Enum.name () .

Deuxièmement, notre champ d' étiquette est définitif . Bien que les champs d'une énumération ne soient pas nécessairement définitifs , dans la majorité des cas, nous ne voulons pas que nos étiquettes changent. Dans l'esprit des valeurs d' énumération constantes, cela a du sens.

Enfin, le champ d' étiquette est public. Ainsi, nous pouvons accéder directement au label:

System.out.println(BE.label);

D'autre part, le champ peut être privé , accessible avec une méthode getLabel () . Par souci de concision, cet article continuera à utiliser le style de champ public.

4. Localisation des valeurs Java Enum

Java fournit une méthode valueOf (String) pour tous les types enum . Ainsi, nous pouvons toujours obtenir une valeur enum basée sur le nom déclaré:

assertSame(Element.LI, Element.valueOf("LI"));

Cependant, nous pouvons également rechercher une valeur d' énumération par notre champ d'étiquette. Pour ce faire, nous pouvons ajouter une méthode statique :

public static Element valueOfLabel(String label) { for (Element e : values()) { if (e.label.equals(label)) { return e; } } return null; }

La méthode statique valueOfLabel () itère les valeurs de l' élément jusqu'à ce qu'elle trouve une correspondance. Il renvoie null si aucune correspondance n'est trouvée. Inversement, une exception pourrait être levée au lieu de renvoyer null .

Voyons un exemple rapide utilisant notre méthode valueOfLabel () :

assertSame(Element.LI, Element.valueOfLabel("Lithium"));

5. Mise en cache des valeurs de recherche

Nous pouvons éviter d'itérer les valeurs d' énumération en utilisant une carte pour mettre en cache les étiquettes . Pour ce faire, nous définissons une carte finale statique et la remplissons lorsque la classe se charge:

public enum Element { // ... enum values private static final Map BY_LABEL = new HashMap(); static { for (Element e: values()) { BY_LABEL.put(e.label, e); } } // ... fields, constructor, methods public static Element valueOfLabel(String label) { return BY_LABEL.get(label); } }

En raison de la mise en cache, les valeurs d' énumération ne sont itérées qu'une seule fois et la méthode valueOfLabel () est simplifiée.

Comme alternative, nous pouvons construire paresseusement le cache lors de son premier accès dans la méthode valueOfLabel () . Dans ce cas, l'accès à la carte doit être synchronisé pour éviter les problèmes de concurrence.

6. Association de plusieurs valeurs

Le constructeur Enum peut accepter plusieurs valeurs . Pour illustrer, ajoutons le numéro atomique en tant qu'int et le poids atomique en tant que flotteur :

public enum Element { H("Hydrogen", 1, 1.008f), HE("Helium", 2, 4.0026f), // ... NE("Neon", 10, 20.180f); private static final Map BY_LABEL = new HashMap(); private static final Map BY_ATOMIC_NUMBER = new HashMap(); private static final Map BY_ATOMIC_WEIGHT = new HashMap(); static { for (Element e : values()) { BY_LABEL.put(e.label, e); BY_ATOMIC_NUMBER.put(e.atomicNumber, e); BY_ATOMIC_WEIGHT.put(e.atomicWeight, e); } } public final String label; public final int atomicNumber; public final float atomicWeight; private Element(String label, int atomicNumber, float atomicWeight) { this.label = label; this.atomicNumber = atomicNumber; this.atomicWeight = atomicWeight; } public static Element valueOfLabel(String label) { return BY_LABEL.get(label); } public static Element valueOfAtomicNumber(int number) { return BY_ATOMIC_NUMBER.get(number); } public static Element valueOfAtomicWeight(float weight) { return BY_ATOMIC_WEIGHT.get(weight); } }

De même, nous pouvons ajouter toutes les valeurs que nous voulons à l' énumération , telles que les symboles de casse appropriés, «He», «Li» et «Be», par exemple.

De plus, nous pouvons ajouter des valeurs calculées à notre énumération en ajoutant des méthodes pour effectuer des opérations.

7. Contrôle de l'interface

Suite à l'ajout de champs et de méthodes à notre énumération , nous avons changé son interface publique. Par conséquent, notre code, qui utilise les méthodes de base Enum name () et valueOf () , ne connaîtra pas nos nouveaux champs.

La méthode statique valueOf () est déjà définie pour nous par le langage Java. Par conséquent, nous ne pouvons pas fournir notre propre implémentation valueOf () .

De même, comme la méthode Enum.name () est définitive, nous ne pouvons pas non plus la remplacer .

Par conséquent, il n'y a aucun moyen pratique d'utiliser nos champs supplémentaires à l'aide de l' API Enum standard . Examinons plutôt différentes manières d'exposer nos champs.

7.1. Remplacer toString ()

La substitution de toString () peut être une alternative à la substitution de name () :

@Override public String toString() { return this.label; }

Par défaut, Enum.toString () renvoie la même valeur que Enum.name ().

7.2. Implémentation d'une interface

Le type enum en Java peut implémenter des interfaces . Bien que cette approche ne soit pas aussi générique que l' API Enum , les interfaces nous aident à généraliser.

Considérons cette interface:

public interface Labeled { String label(); }

Par souci de cohérence avec la méthode Enum.name () , notre méthode label () n'a pas de préfixe get .

Et, comme la méthode valueOfLabel () est statique , nous ne l'incluons pas dans notre interface.

Enfin, nous pouvons implémenter l'interface dans notre enum :

public enum Element implements Labeled { // ... @Override public String label() { return label; } // ... }

L'un des avantages de cette approche est que l' interface étiquetée peut être appliquée à n'importe quelle classe, pas seulement aux types enum . Au lieu de nous fier à l' API Enum générique , nous avons maintenant une API plus spécifique au contexte.

8. Conclusion

Dans cet article, nous avons exploré de nombreuses fonctionnalités de l' implémentation Java Enum . En ajoutant des constructeurs, des champs et des méthodes, nous voyons que l' énumération peut faire beaucoup plus que des constantes littérales.

Comme toujours, le code source complet de cet article est disponible sur GitHub.