1. Introduction
En Java, il existe des collections spécifiques pour travailler avec les énumérations (enum) : EnumSet et EnumMap. Elles font partie de la bibliothèque standard (java.util) et sont conçues pour une efficacité maximale avec les types enum.
Comment sont conçus EnumSet et EnumMap en interne ?
EnumSet fonctionne comme un masque binaire. Imaginez un ensemble d’interrupteurs où chaque élément d’énumération occupe exactement un bit. Si le bit est activé — l’élément est présent dans l’ensemble ; s’il est désactivé — il n’y est pas. Tout est stocké dans un tableau de nombres (long[]), et si votre enum comporte moins de 64 valeurs, l’ensemble entier tient dans un seul nombre !
EnumMap est encore plus simple : c’est un tableau de valeurs dont l’indice est le numéro ordinal (ordinal) de l’élément enum. Au lieu d’un HashMap<Enum, V> habituel, vous obtenez une structure très compacte et rapide.
Qu’est-ce que cela apporte ? Les opérations d’ajout, de suppression et de vérification s’exécutent en O(1). La mémoire utilisée est minimale (surtout comparée à HashSet et HashMap). L’itération des éléments suit toujours l’ordre de déclaration dans l’enum, ce qui rend le résultat prédictible.
Exemple : à quoi cela ressemble
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
EnumSet<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
System.out.println(weekend); // [SAT, SUN]
De l’extérieur, c’est un ensemble classique. Mais en interne — un nombre avec deux bits positionnés : l’un pour SAT, l’autre pour SUN. Ajoutez FRI — un bit supplémentaire sera activé. Pas de table de hachage ni d’objets superflus.
EnumMap<Day, String> schedule = new EnumMap<>(Day.class);
schedule.put(Day.MON, "Gym");
schedule.put(Day.FRI, "Party");
System.out.println(schedule); // {MON=Gym, FRI=Party}
Ici, les clés (Day) sont converties en indices internes de tableau ; l’accès est donc aussi rapide qu’un accès à un élément de tableau.
2. Cas d’utilisation : drapeaux, tableaux, automates finis
EnumSet : idéal pour les drapeaux et ensembles d’états
- Drapeaux : stocker des ensembles « activé/désactivé » pour un nombre limité d’options.
- Ensembles de valeurs enum : jours de la semaine, permissions utilisateur, états d’une tâche.
- Automates finis (FSM) : pratique pour stocker les transitions autorisées dans un EnumSet.
Exemple : indicateurs d’accès
enum Permission { READ, WRITE, EXECUTE }
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.WRITE)) {
// Écriture autorisée
}
Exemple : toutes les valeurs sauf certaines
EnumSet<Day> workdays = EnumSet.complementOf(EnumSet.of(Day.SAT, Day.SUN));
System.out.println(workdays); // [MON, TUE, WED, THU, FRI]
EnumMap : idéal pour les tableaux indexés par des clés enum
- Tables de correspondance : les clés sont des valeurs enum, les valeurs — n’importe quels objets.
- Accès rapide : plus rapide et plus compact qu’un HashMap<Enum, V>.
Exemple : prix par jour de la semaine
EnumMap<Day, Integer> prices = new EnumMap<>(Day.class);
prices.put(Day.MON, 100);
prices.put(Day.SAT, 200);
System.out.println(prices.get(Day.SAT)); // 200
Exemple : automate fini
enum State { START, RUNNING, STOPPED }
EnumMap<State, EnumSet<State>> transitions = new EnumMap<>(State.class);
transitions.put(State.START, EnumSet.of(State.RUNNING));
transitions.put(State.RUNNING, EnumSet.of(State.STOPPED));
transitions.put(State.STOPPED, EnumSet.noneOf(State.class));
3. Pièges : modification de l’enum et sérialisation
EnumSet et EnumMap dépendent de l’ensemble et de l’ordre des valeurs de votre enum. Si vous ajoutez un nouvel élément, supprimez un ancien ou en changez l’ordre, les collections sauvegardées ou sérialisées peuvent se comporter de manière incorrecte.
Sérialisation
- EnumSet et EnumMap sont sérialisables, mais si l’enum change entre sérialisation et désérialisation, des erreurs/pertes de données sont possibles.
- La suppression d’une valeur de l’enum après sérialisation conduit presque à coup sûr à une exception lors de la lecture.
Bonne pratique :
- Ne sérialisez pas EnumSet/EnumMap si vous n’êtes pas certain que l’enum est stable.
- Pour un stockage à long terme, utilisez par exemple une liste de représentations sous forme de chaîne des valeurs.
4. Détails utiles
Comparaison d’EnumSet/EnumMap et des collections classiques
| Collection | Clé/élément | Implémentation interne | Performances | Mémoire | Null |
|---|---|---|---|---|---|
|
|
masque binaire | O(1) | très peu | interdit |
|
|
table de hachage | O(1) | plus | autorisé |
|
|
tableau indexé par ordinal | O(1) | très peu | interdit |
|
|
table de hachage | O(1) | plus | autorisé |
Bonnes pratiques
- Utilisez EnumSet pour les ensembles de valeurs (of, noneOf, allOf, complementOf).
- Utilisez EnumMap pour les tables d’association où la clé est un enum.
- Évitez les énumérations « géantes » — des listes de centaines de valeurs nuisent à la compacité.
- Ne sérialisez pas si l’enum peut changer.
- N’utilisez pas null comme clé ou valeur.
5. Pratique : comment utiliser EnumSet et EnumMap dans une application
Exemple : stockage des rôles utilisateur
enum Role { USER, ADMIN, MODERATOR }
class User {
private EnumSet<Role> roles = EnumSet.noneOf(Role.class);
public void addRole(Role role) {
roles.add(role);
}
public boolean isAdmin() {
return roles.contains(Role.ADMIN);
}
}
Exemple : table de transitions d’états
enum State { NEW, IN_PROGRESS, DONE }
EnumMap<State, EnumSet<State>> transitions = new EnumMap<>(State.class);
transitions.put(State.NEW, EnumSet.of(State.IN_PROGRESS));
transitions.put(State.IN_PROGRESS, EnumSet.of(State.DONE));
transitions.put(State.DONE, EnumSet.noneOf(State.class));
6. Erreurs courantes avec EnumSet/EnumMap
Erreur n° 1 : Utiliser EnumSet/EnumMap avec un type non enum.
Ces collections ne fonctionnent qu’avec des types qui sont des enum.
// EnumSet<String> set = EnumSet.of("A", "B"); // Erreur de compilation !
Erreur n° 2 : Utiliser EnumSet/EnumMap pour des enum très volumineux.
Des énumérations de plusieurs centaines de valeurs réduisent la compacité. Néanmoins, cela reste souvent meilleur en mémoire qu’un HashSet/HashMap pour les mêmes données.
Erreur n° 3 : Modifier l’enum après sérialisation.
Ajouter/supprimer/réordonner des valeurs après la sérialisation entraîne des erreurs à la lecture ou une perte de données.
Erreur n° 4 : Utiliser EnumSet/EnumMap avec null.
Ni les éléments de EnumSet, ni les clés/valeurs de EnumMap ne peuvent être null.
EnumSet<Day> days = EnumSet.of(null); // NullPointerException!
EnumMap<Day, String> map = new EnumMap<>(Day.class);
map.put(null, "test"); // NullPointerException!
Erreur n° 5 : Penser qu’EnumSet est un Set « ordinaire ».
EnumSet ne stocke que les valeurs de son enum ; vous ne pouvez pas y ajouter un objet extérieur à l’énumération.
Erreur n° 6 : Utiliser EnumSet/EnumMap avec des enum « modifiables ».
Si des énumérations sont générées ou substituées à l’exécution (chargement dynamique/réflexion), ces structures ne fonctionneront pas correctement.
GO TO FULL VERSION