MokaByte 93 - Febbraio 2005
Java 5
Le enums
di
Andrea
Gini
Dopo aver trattato diffusamente i Generics, la novità più importante di Java 5, è ora di imparare il funzionamento delle enums, la seconda più importante novità della nuova distribuzione di Java. Grazie alle enums è ora disponibile un meccanismo unificato e sicuro per definire e gestire insiemi omogenei di dati; in più, grazie ad alcune funzionalità esclusive dell'implementazione Java di questo costrutto, è possibile estendere gli elementi di una enum con metodi e attributi, una funzionalità che, a costo di una difficoltà sintattica veramente minima, offre delle possibilità enormi.

Il problema degli insiemi
Durante la programmazione emerge spesso la necessità di definire insiemi omogenei di costanti, che denotano insiemi presenti nella realtà. Alcuni esempi sono i giorni della settimana (lunedì, martedì, mercoledì e così via), i punti cardinali (nord, sud, est, ovest), le stagioni (autunno, inverno, primavera, estate) e così via. Dal momento che l'iterazione è uno dei costrutti fondamentali della programmazione, non deve stupire che molti programmi abbiano la necessità di iterare tra simili insiemi per effettuale calcoli contabili, statistici e quant'altro.

Fino ad ora la maniera più usata per definire insiemi prevedeva l'uso di costanti numeriche:

public class Stagioni {
public static final int AUTUNNO = 0;
public static final int INVERNO = 1;
public static final int PRIMAVERA = 2;
public static final int ESTATE = 3;
….
}

La creazione di insiemi mediante l'uso di costanti numerica prevede una chiamata esplicita alla variabile statica della classe contenitrice. Nel seguente esempio si immagina l'uso di una classe Viaggio che richiede come parametri di costruttore due costanti di tipo Nazione e uno di tipo Stagioni:

Viaggio v = new Viaggio(Nazioni.ITALIA,Nazioni.USA,Stgioni.ESTATE);

Questa implementazione presenta numerosi svantaggi: anzitutto non è tipizzata, dal momento che alla fine il valore reale delle costanti è un numero intero: se nella precedente chiamata si inverte in qualche modo l'ordine delle costanti, il compilatore non segnalerà alcun errore, e il programma avrà un'esecuzione indefinita. In secondo luogo non è possibile estendere o modificare l'ordine delle costanti senza essere costretti ricompilare il codice client, che continuerà a funzionare, anche se in modo diverso dal previsto, dal momento che le costanti vengono compilate come semplici numeri interi. Infine la stampa dei valori non contiene nessuna informazione riguardo al valore simbolico della costante rappresentata.
Molti linguaggi, tra cui Pascal, Visual Basic e C#, prevedono un costrutto apposito per definire insiemi, ma la loro reale implementazione non è poi tanto diversa da quella descritta qui sopra.
Java 5, al contrario, prevede un costrutto enum particolarmente completo ed interessante, che verrà analizzato nei prossimi paragrafi.

 

Le enums in Java
La parola chiave enum permette di definire insiemi ricorrendo ad una sintassi molto semplice:

enum GiorniDellaSettimana {lunedì, martedì, mercoledì, giovedì, venerdì, sabato, domenica}
enum PuntiCardinali {nord, sud, est, ovest}
enum Stagioni {autunno, inverno, primavera, estate}

Le enum appena definite sono dei veri e propri tipi, che è possibile usare nei parametri di un metodo, per evitare i problemi descritti nel paragrafo precedente:

class Viaggio {
public Viaggio(Destinazione partenza, Destinazione arrivo, Stagioni stagione) {

}
...
}

Ma le enums in Java hanno altre proprietà, legate al fatto che esse vengono implementate con vere e proprie classi, che supportano la serializzazione e la comparazione attraverso l'interfaccia Comparable. La serializzazione garantisce la compatibilità all'indietro qualora venga modificato l'ordine o il numero di elementi di una enum. Si provi a modificare la enum PuntiCardinali descritta precedentemente nel seguente modo, aggiungendo elementi e cambiando l'ordine di quelli esistenti:

enum PuntiCardinali {nord, nordest, est, sudest,sud, sudovest, ovest,nordovest}

Non solo i programmi che ne fanno uso continueranno a funzionare perfettamente senza bisogno di ricompilazione, ma nemmeno le eventuali occorrenze presenti in oggetti serializzati perderanno valore.

Ogni costante è presente in singola copia in memoria, per cui è possibile comparare due valori anche usando l'operatore ==, molto più efficiente del metodo equals(). Il metodo toString() restituisce il valore simbolico della costante, che può essere quindi stampato in report o tabelle. Nei prossimi paragrafi analizzeremo ulteriori aspetti interessanti delle enums.

 

Il metodo values()
Ogni enum presenta un metodo statico values() che restituisce un array contenente tutti i valori del tipo enum nell'ordine in cui sono stati definiti. In questo modo diventa estremamente facile iterare tra gli elementi di una enum per effettuare una elaborazione, specie ricorrendo al nuovo costrutto foreac di Java 5 (cfr [1]):

for(GiorniDellaSettimana g : GiorniDellaSettimana.values())
System.out.println(g);

Si noti la comodità di poter stampare direttamente il valore simbolico della costante. Oltre al metodo values(), ogni enum presenta i propri elementi come variabili static final, un fattore che, come vedremo più avanti, può rivelarsi utile in molti frangenti.

 

Enum provviste di comportamento
E' anche possibile, ricorrendo ad una sintassi molto simile a quella di una normale classe, definire una enum i cui elementi posseggono metodi ed attributi:

// Trenta giorni ha Novembre, con April, Giugno e Settembre…
public enum Mesi {
gennaio(31), febbraio(28), marzo(31),
aprile(30),maggio(31),giugno(30),
luglio(31),agosto(31),settembre(30),
ottobre(31),novembre(30),dicembre(31);

private final int giorni; // attributo

Mesi(int giorni) { // costruttore
this. giorni = giorni;
}
public int getGiorni() { // metodo
return giorni;
}
}

In questo esempio l'enum Mesi denota un tipo caratterizzato dai valori gennaio, febbraio, marzo, aprile, maggio, giugno, luglio, agosto, settembre, ottobre, novembre, dicembre. Ciascun elemento dell'insieme è a sua volta caratterizzato da un differente valore dell'attributo giorni, che può essere letto attraverso il metodo getGiorni(), comune a tutti gli elementi dell'insieme:

for (Mesi m : Mesi.values())
System.out.println("Il mese" + m + "ha "+m.getGiorni()+" giorni");

In pratica una enum permette di definire, con un costrutto compatto, una classe, i suoi attributi, i suoi metodi e l'insieme di tutte le sue possibili istanze che, come già precisato, ha cardinalità finita.

 

Metodi constant-specific
In certe circostanze si desidera fare in modo che ogni costante dell'insieme disponga di un comportamento specifico di un metodo comune. In questo caso è sufficiente definire abstract il metodo e definire una versione del metodo specifica per ogni elemento.
Ecco un esempio:

enum MyEnum {

primoElemento {
metodo() {
// prima implementazione
}
},
secondoElemento {
metodo() {
// seconda implementazione
}
}
...

abstract void metodo();
}

 

EnumSet ed EnumMap
A supporto delle enum sono stati aggiunte due classi al package java.util: EnumSet ed EnumMap. EnumSet è un'implementazione dell'interfaccia Set specifica per fornire elevate prestazioni con le enum. Internamente lavora su singoli bit: spesso un'unica variabile long è sufficiente a tenere traccia di tutti gli elementi di una enum, dal momento che è difficile che vengano definiti insiemi con più di 64 valori. EnumSet viene usata principalmente per iterare tra un sottoinsieme dei valori di un insieme, grazie al metodo statico range():

static <E extends Enum<E>> EnumSet<E> range(E from, E to)

La sintassi di questo metodo è un po' complessa, a seguito dell'uso di tipi parametrici nidificati: per chiarire le cose ' sufficiente precisare che il tipo Enum<E> è la classe da cui discendono tutte le enum. L'uso pratico risulta abbastanza semplice, dal momento che il tipo in questione viene dedotto in maniera automatica dal tipo dei parametri; si provi ad immaginare un frammento di un programma che deve elaborare buste paga e che pertanto deve lavorare solo sui giorni feriali trascurando i giorni festivi:

for(GiorniDellaSettimana g : EnumSet.range(GiorniDellaSettimana.lunedì, GiorniDellaSettimana.venerdì))
calcolaBustaPaga(g);

Come si vede il tipo parametrico viene dedotto dai parametri, che devono essere due elementi della stessa enum. Si noti che gli elementi vengono presi come membri statici direttamente dalla enum di appartenenza. Un altro metodo interessante il metodo copyOf(), che restituisce un EnumSet a partire dagli elementi presenti in una Collection che a sua volta deve contenere elementi appartenenti ad una enum preesistente:

static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)

La classe EnumMap è una speciale realizzazione dell'interfaccia Map, che permette di creare mappe hash le cui chiavi appartengono ad una enum. L'efficienza è garantita da un'implementazione interna che sfrutta adeguatamente un array per effettuare le dovute associazioni: pertanto ne viene consigliato l'utilizzo in tutti quei casi in cui sia necessario creare mappe hash basate su enum.

 

Conclusioni
Questo mese abbiamo imparato a sfruttare a fondo il nuovo costrutto enum presente in Java 5. Dopo aver visto gli usi più banali, abbiamo illustrato la definizione di enum dotate di comportamento e alcune classi di supporto utili nella gestione delle enum stesse. Nel prossimo articolo tratteremo le annotation, un ulteriore nuovo costrutto di Java5.

 

Bibliografia
[1] Andrea Gini, "Java2 Standard Edition 1.5 beta 1", Mokabyte 3/2004
http://www.mokabyte.it/2004/03/jdk1_5.htm

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it