Un insieme fisso di valori con nome
Alcuni valori hanno senso solo come elemento di una lista piccola e nota: i giorni della settimana, i semi di un mazzo di carte, gli stati in cui può trovarsi un ordine. Potresti rappresentarli con codici int (0 per in attesa, 1 per spedito) o con stringhe ("PENDING", "SHIPPED"), ma niente impedisce a chi chiama il codice di passare 7 o "PNEDING". Un enum chiude questa falla: il tipo stesso ammette solo i valori che hai dichiarato.
Day è un tipo completamente nuovo. Una variabile di tipo Day può contenere esattamente una di quelle sette costanti, oppure null, e nient'altro. Il compilatore rifiuta Day d = "WEDNESDAY"; o Day d = 2;. Questa garanzia è tutto il senso dell'enum.
Ogni costante (Day.MONDAY e così via) è una singola istanza condivisa, creata una volta sola. Dato che esiste un solo MONDAY, gli enum si confrontano con == invece che con equals: sono lo stesso oggetto.
Switch su un enum
Gli enum si abbinano in modo naturale con switch. Dentro lo switch non devi nemmeno qualificare ogni costante con il nome del tipo:
Dentro lo switch, scrivi case SATURDAY, non case Day.SATURDAY: Java deduce il tipo. Il grande vantaggio rispetto a uno switch su una stringa o un int è che, se in seguito aggiungi una nuova costante, l'IDE e i controlli di completezza possono segnalarti ogni switch che si è dimenticato di gestirla.
Scorrere tutte le costanti
Ogni enum riceve automaticamente un metodo statico values() che restituisce tutte le costanti nell'ordine di dichiarazione, più un ordinal() che dà la posizione di ogni costante a partire da zero:
name() restituisce l'identificatore della costante come String, e values() è perfetto per costruire menu o scorrere le opzioni. Una raccomandazione: non salvare ordinal() in nulla di persistente. Se qualcuno in seguito riordina le costanti, i numeri cambiano e i tuoi dati salvati si rompono. Considera ordinal() un dettaglio implementativo, non un ID stabile.
Gli enum sono vere classi: campi e metodi
È qui che gli enum di Java vanno oltre gli enum di interi con nome di altri linguaggi. Ogni costante può portare con sé dei dati, passati tramite un costruttore, e l'enum può avere dei metodi. Elenchi prima le costanti, dai a ciascuna i suoi argomenti per il costruttore, poi dichiari campi, costruttore e metodi dopo un punto e virgola.
Nota la struttura: le costanti vengono per prime e terminano con un punto e virgola, poi segue il resto della classe. Il costruttore è implicitamente private: non puoi mai scrivere new Planet(...), perché le uniche istanze ammesse sono quelle dichiarate in cima. I campi final rendono immutabile ogni costante, ed è proprio ciò che vuoi per dei singleton condivisi.
Da una stringa e ritorno
Per trasformare una stringa nella costante corrispondente, usa il metodo generato automaticamente valueOf:
valueOf cerca il nome della costante in modo esatto, maiuscole comprese, e lancia IllegalArgumentException se non trova corrispondenze. È la trappola più comune: "shipped" non corrisponde a SHIPPED. Quando analizzi input dell'utente o dati esterni, normalizza prima le maiuscole (input.toUpperCase()) e racchiudi la chiamata in un try/catch, altrimenti il programma si blocca al primo valore sbagliato.
Comportamento specifico per ogni costante
A volte ogni costante deve comportarsi in modo diverso, non solo contenere dati diversi. Puoi dare a un enum un metodo astratto e far fornire a ogni costante la propria implementazione in un piccolo corpo:
Questo schema a volte si chiama "metodo specifico per costante". Sostituisce un lungo switch sulla costante con un comportamento legato direttamente a ciascuna: aggiungi una nuova operazione e il compilatore ti obbliga a definirne l'apply, così non puoi dimenticare un caso.
Quando usare un enum
Usa un enum ogni volta che un valore appartiene per natura a un insieme piccolo, fisso e noto in fase di compilazione:
- Gli stati di un flusso di lavoro (
PENDING,SHIPPED,DELIVERED). - Categorie, modalità o tipi (
READ,WRITE,EXECUTE). - Qualsiasi punto in cui saresti tentato di definire un gruppo di costanti
public static final int: l'enum ti dà gli stessi valori con nome più sicurezza sui tipi, untoStringleggibile e il supporto aswitch, senza costi aggiuntivi.
Non usare un enum per un insieme aperto o determinato a runtime (nomi utente, un elenco di paesi letto da un database). Gli enum sono fissati in fase di compilazione; se l'insieme cambia mentre il programma gira, ti serve invece una normale collezione.
Prossimo passo: i generics
Hai visto values() restituire un Planet[] e le pagine precedenti usare List<Shape>: quella sintassi <...> sono i generics, il modo in cui Java ti permette di scrivere una classe o un metodo che funziona con molti tipi mantenendo una completa sicurezza sui tipi. Nella prossima pagina vedremo come funzionano davvero List<String> e Map<K, V> e come scrivere i tuoi tipi generici.
Domande frequenti
Cos'è un enum in Java?
Un enum è un tipo speciale il cui valore può essere solo una di un insieme fisso di costanti con nome, come Day.MONDAY o Status.ACTIVE. Ogni costante è una singola istanza dell'enum già costruita. Gli enum ti danno sicurezza sui tipi: un metodo che riceve un Day può ricevere solo un giorno vero, mai una stringa scritta male o un int fuori intervallo.
Un enum Java può avere campi e metodi?
Sì. Un enum è una classe a tutti gli effetti. Puoi dare a ogni costante degli argomenti per il costruttore, memorizzarli in campi private final e aggiungere metodi che li usano. Per esempio MERCURY(3.3e23, 2.4e6) passa massa e raggio al costruttore dell'enum, e un metodo surfaceGravity() può fare calcoli a partire da quei campi.
Qual è la differenza tra values() e valueOf() in un enum Java?
values() restituisce un array con tutte le costanti dell'enum, nell'ordine di dichiarazione: comodo per scorrere ogni opzione. valueOf("NAME") fa il contrario: cerca la costante il cui nome corrisponde esattamente alla stringa, e lancia IllegalArgumentException se non ne trova nessuna. Entrambi sono generati automaticamente dal compilatore.