CodeGym /Corsi /JAVA 25 SELF /EnumSet/EnumMap

EnumSet/EnumMap

JAVA 25 SELF
Livello 28 , Lezione 3
Disponibile

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
EnumSet
enum
maschera di bit O(1) molto poca non consentito
HashSet<Enum>
enum
tabella hash O(1) maggiore consentito
EnumMap
enum
array indicizzato per ordinal O(1) molto poca non consentito
HashMap<Enum, V>
enum
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.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION