1. Introduzione
In Java esistono collezioni speciali per lavorare con gli elenchi (enum): EnumSet e EnumMap. Fanno parte della libreria standard (java.util) e sono pensate per lavorare nel modo più efficiente possibile con i tipi enum.
Come sono implementati internamente EnumSet ed EnumMap?
EnumSet funziona come una maschera di bit. Immaginate un insieme di flag, dove ogni elemento dell'enum occupa esattamente un bit. Se il bit è impostato — l'elemento è presente nell'insieme, se è disattivato — non è presente. Tutto è memorizzato in un array di numeri (long[]), e se il vostro enum ha meno di 64 valori, l'intero insieme sta in un unico numero!
EnumMap è ancora più semplice: è un array di valori, dove l'indice è il numero ordinale (ordinal) dell'elemento enum. Invece del consueto HashMap<Enum, V> ottenete una struttura molto compatta e veloce.
Cosa ne deriva? Le operazioni di aggiunta, rimozione e verifica avvengono in O(1). Si usa il minimo della memoria (soprattutto in confronto a HashSet e HashMap). L'iterazione degli elementi è sempre nell'ordine di dichiarazione nell'enum, il che rende il risultato prevedibile.
Esempio: come si presenta
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
EnumSet<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
System.out.println(weekend); // [SAT, SUN]
All'esterno è un normale insieme. Ma internamente è un numero in cui sono impostati due bit: uno per SAT, l'altro per SUN. Se aggiungete FRI — si attiverà un altro bit. Niente tabella hash né oggetti superflui.
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}
Qui le chiavi (Day) vengono trasformate in indici interni dell'array, quindi l'accesso funziona veloce quanto l'accesso a un elemento di array.
2. Casi d'uso: flag, tabelle, automi finiti
EnumSet: ideale per flag e insiemi di stati
- Flag: memorizzare insiemi «attivo/disattivo» per un numero limitato di opzioni.
- Insiemi di valori enum: giorni della settimana, permessi dell'utente, stati di un'attività.
- Automi finiti (FSM): comodo memorizzare le transizioni ammesse in EnumSet.
Esempio: flag di accesso
enum Permission { READ, WRITE, EXECUTE }
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.WRITE)) {
// Scrittura consentita
}
Esempio: tutti i valori tranne alcuni
EnumSet<Day> workdays = EnumSet.complementOf(EnumSet.of(Day.SAT, Day.SUN));
System.out.println(workdays); // [MON, TUE, WED, THU, FRI]
EnumMap: ideale per tabelle con chiavi enum
- Tabelle di corrispondenza: le chiavi sono valori dell'enum, i valori — oggetti di qualsiasi tipo.
- Accesso veloce: più rapido e compatto di HashMap<Enum, V>.
Esempio: prezzi per giorno della settimana
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
Esempio: automa finito
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. Insidie: modifica degli enum e serializzazione
EnumSet e EnumMap dipendono dalla composizione e dall'ordine dei valori del vostro enum. Se aggiungete un nuovo elemento, ne rimuovete uno vecchio o li riordinate, le collezioni salvate o serializzate possono comportarsi in modo scorretto.
Serializzazione
- EnumSet e EnumMap sono serializzabili, ma in caso di modifica dell'enum tra serializzazione e deserializzazione sono possibili errori/perdita di dati.
- La rimozione di un valore dall'enum dopo la serializzazione quasi certamente porterà a un'eccezione in lettura.
Best practice:
- Non serializzate EnumSet/EnumMap se non siete certi che l'enum sia stabile.
- Per la conservazione a lungo termine usate, ad esempio, un elenco di rappresentazioni testuali dei valori.
4. Dettagli utili
Confronto tra EnumSet/EnumMap e le collezioni comuni
| Collezione | Chiave/elemento | Struttura interna | Prestazioni | Memoria | Null |
|---|---|---|---|---|---|
|
|
maschera di bit | O(1) | molto poca | non consentito |
|
|
tabella hash | O(1) | maggiore | consentito |
|
|
array indicizzato per ordinal | O(1) | molto poca | non consentito |
|
|
tabella hash | O(1) | maggiore | consentito |
Best practice
- Usate EnumSet per insiemi di valori (of, noneOf, allOf, complementOf).
- Usate EnumMap per tabelle associative, dove la chiave è un enum.
- Evitate enum «giganteschi» — elenchi con centinaia di valori riducono la compattezza.
- Non serializzate, se l'enum può cambiare.
- Non usate null come chiave o valore.
5. Pratica: come usare EnumSet e EnumMap in un'applicazione
Esempio: memorizzazione dei ruoli utente
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);
}
}
Esempio: tabella delle transizioni di stato
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. Errori comuni nell'uso di EnumSet/EnumMap
Errore n. 1: utilizzo di EnumSet/EnumMap con un tipo non enum.
Queste collezioni funzionano solo con tipi che sono enum.
// EnumSet<String> set = EnumSet.of("A", "B"); // Errore di compilazione!
Errore n. 2: usare EnumSet/EnumMap per enum molto grandi.
Enum con centinaia di valori riducono la compattezza. Tuttavia, spesso restano più efficienti in memoria rispetto a HashSet/HashMap per gli stessi dati.
Errore n. 3: modificare l'enum dopo la serializzazione.
L'aggiunta/rimozione/riordinamento di valori dopo la serializzazione porta a errori in lettura o a perdita di dati.
Errore n. 4: usare EnumSet/EnumMap con null.
Né gli elementi di EnumSet, né le chiavi/valori di EnumMap possono essere null.
EnumSet<Day> days = EnumSet.of(null); // NullPointerException!
EnumMap<Day, String> map = new EnumMap<>(Day.class);
map.put(null, "test"); // NullPointerException!
Errore n. 5: aspettarsi che EnumSet sia un Set «normale».
EnumSet contiene solo i valori del proprio enum; non potete aggiungere un oggetto arbitrario al di fuori dell'enum.
Errore n. 6: usare EnumSet/EnumMap con enum «mutabili».
Se gli elenchi vengono generati o sostituiti a runtime (caricamento dinamico/riflessione), queste strutture non funzioneranno correttamente.
GO TO FULL VERSION