Come usare le enumerazioni typesafe in Java
Il codice Java che utilizza i tipi enumerati tradizionali è problematico. Java 5 ci ha fornito un'alternativa migliore sotto forma di enumerazioni typesafe. In questo articolo, ti presento i tipi enumerati e le enumerazioni typesafe, ti mostro come dichiarare un'enumerazione typesafe e usarla in un'istruzione switch e discuto la personalizzazione di un'enumerazione typesafe aggiungendo dati e comportamenti. Concludo l'articolo esplorando la classe.java.lang.Enum
Dai tipi enumerati alle enumerazioni typesafe
Un tipo enumerato specifica un insieme di costanti correlate come valori. Gli esempi includono una settimana di giorni, le direzioni standard della bussola nord / sud / est / ovest, i tagli delle monete di una valuta e i tipi di token di un analizzatore lessicale.
I tipi enumerati sono stati tradizionalmente implementati come sequenze di costanti intere, come dimostrato dal seguente insieme di costanti di direzione:
int finale statico DIR_NORTH = 0; int finale statico DIR_WEST = 1; int finale statico DIR_EAST = 2; int finale statico DIR_SOUTH = 3;
Ci sono diversi problemi con questo approccio:
- Mancanza di sicurezza dei tipi: poiché una costante di tipo enumerata è solo un numero intero, è possibile specificare qualsiasi numero intero in cui è richiesta la costante. Inoltre, addizioni, sottrazioni e altre operazioni matematiche possono essere eseguite su queste costanti; per esempio,
(DIR_NORTH + DIR_EAST) / DIR_SOUTH
), che non ha senso. - Spazio dei nomi non presente: le costanti di un tipo enumerato devono essere precedute da un qualche tipo di identificatore univoco (si spera) (ad esempio
DIR_
) per evitare collisioni con le costanti di un altro tipo enumerato. - Fragilità: poiché le costanti di tipo enumerate vengono compilate in file di classe in cui sono memorizzati i loro valori letterali (in pool di costanti), la modifica del valore di una costante richiede che questi file di classe e quei file di classe dell'applicazione che dipendono da essi vengano ricostruiti. In caso contrario, si verificherà un comportamento indefinito in fase di esecuzione.
- Mancanza di informazioni: quando viene stampata una costante, viene visualizzato il suo valore intero. Questo output non dice nulla su ciò che rappresenta il valore intero. Non identifica nemmeno il tipo enumerato a cui appartiene la costante.
È possibile evitare i problemi di "mancanza di protezione dai tipi" e "mancanza di informazioni" utilizzando le java.lang.String
costanti. Ad esempio, potresti specificare static final String DIR_NORTH = "NORTH";
. Sebbene il valore della costante sia più significativo, le String
costanti basate su-soffrono ancora di "spazio dei nomi non presente" e problemi di fragilità. Inoltre, a differenza dei confronti di interi, non è possibile confrontare i valori di stringa con gli operatori ==
e !=
(che confrontano solo i riferimenti).
Questi problemi hanno indotto gli sviluppatori a inventare un'alternativa basata su classi nota come Typesafe Enum . Questo modello è stato ampiamente descritto e criticato. Joshua Bloch ha introdotto il modello nell'articolo 21 della sua Effective Java Programming Language Guide (Addison-Wesley, 2001) e ha notato che ha alcuni problemi; vale a dire che è scomodo aggregare le costanti enum typesafe in insiemi e che le costanti di enumerazione non possono essere utilizzate nelle switch
istruzioni.
Considera il seguente esempio del modello enumerazione typesafe. La Suit
classe mostra come potresti usare l'alternativa basata sulla classe per introdurre un tipo enumerato che descrive i quattro semi delle carte (fiori, quadri, cuori e picche):
public final class Suit // Non dovrebbe essere possibile sottoclassare Suit. {pubblico statico finale vestito CLUB = nuovo seme (); pubblico statico finale vestito DIAMANTI = nuovo vestito (); finale statico pubblico Suit HEARTS = new Suit (); finale statico pubblico Suit SPADES = new Suit (); private Suit () {} // Non dovrebbe essere possibile introdurre costanti aggiuntive. }
Per usare questa classe, dovresti introdurre una Suit
variabile e assegnarla a una delle Suit
costanti di, come segue:
Suit suit = Suit.DIAMONDS;
Potresti quindi voler interrogare suit
in una switch
dichiarazione come questa:
switch (suit) {case Suit.CLUBS: System.out.println ("clubs"); rompere; caso Suit.DIAMONDS: System.out.println ("diamanti"); rompere; caso Suit.HEARTS: System.out.println ("cuori"); rompere; caso Suit.SPADES: System.out.println ("spades"); }
Tuttavia, quando il compilatore Java Suit.CLUBS
rileva, segnala un errore che indica che è richiesta un'espressione costante. Potresti provare a risolvere il problema come segue:
switch (suit) {case CLUBS: System.out.println ("clubs"); rompere; caso DIAMANTI: System.out.println ("diamanti"); rompere; case HEARTS: System.out.println ("cuori"); rompere; case SPADES: System.out.println ("spades"); }
Tuttavia, quando il compilatore rileva CLUBS
, riporterà un errore che indica che non è stato in grado di trovare il simbolo. E anche se si inserisce Suit
in un pacchetto, si importa il pacchetto e si importano staticamente queste costanti, il compilatore si lamenta di non poter convertire Suit
a int
quando incontra suit
in switch(suit)
. Per quanto riguarda ciascuno case
, il compilatore segnalerebbe anche che è richiesta un'espressione costante.
Java non supporta il pattern Enum Typesafe con le switch
istruzioni. Tuttavia, ha introdotto la funzionalità del linguaggio enum typesafe per incapsulare i vantaggi del pattern mentre ne risolveva i problemi e questa funzione supporta switch
.
Dichiarare un'enumerazione typesafe e usarla in un'istruzione switch
Una semplice dichiarazione enum typesafe nel codice Java assomiglia alle sue controparti nei linguaggi C, C ++ e C #:
enumerazione Direzione {NORD, OVEST, EST, SUD}
Questa dichiarazione utilizza la parola chiave enum
per introdurre Direction
come enum typesafe (un tipo speciale di classe), in cui è possibile aggiungere metodi arbitrari e implementare interfacce arbitrarie. La NORTH
, WEST
, EAST
, e SOUTH
costanti enum sono implementati come corpi classe specifica costanti che definiscono le classi anonime estendentisi delle allegando Direction
classe.
Direction
e altre enumerazioni typesafe estendono e ereditano vari metodi, tra cui , e , da questa classe. Esploreremo più avanti in questo articolo.Enum
values()
toString()
compareTo()
Enum
Il Listato 1 dichiara la suddetta enumerazione e la utilizza in switch
un'istruzione. Mostra anche come confrontare due costanti enum, per determinare quale costante viene prima dell'altra costante.
Listato 1: TEDemo.java
(versione 1)
public class TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static void main (String [] args) {for (int i = 0; i <Direction.values (). length; i ++) {Direction d = Direction .values () [i]; System.out.println (d); switch (d) {case NORTH: System.out.println ("Move north"); rompere; case WEST: System.out.println ("Move west"); rompere; case EAST: System.out.println ("Move east"); rompere; case SUD: System.out.println ("Spostati a sud"); rompere; default: assert false: "direzione sconosciuta"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}
Il Listato 1 dichiara l' Direction
enumerazione typesafe e itera sui suoi membri costanti, che values()
restituisce. Per ogni valore, l' switch
istruzione (migliorata per supportare enumerazioni typesafe) sceglie il case
che corrisponde al valore di d
e restituisce un messaggio appropriato. (Non si antepone a una costante enum, ad esempio, il NORTH
suo tipo enum.) Infine, il Listato 1 valuta Direction.NORTH.compareTo(Direction.SOUTH)
per determinare se NORTH
viene prima SOUTH
.
Compilare il codice sorgente come segue:
javac TEDemo.java
Eseguire l'applicazione compilata come segue:
java TEDemo
Dovresti osservare il seguente output:
NORD Muovi a nord OVEST Muovi a ovest EST Muovi a est SUD Muovi a sud -3
L'output rivela che il toString()
metodo ereditato restituisce il nome della costante enum e che NORTH
viene prima SOUTH
in un confronto di queste costanti enum.
Aggiunta di dati e comportamenti a un'enumerazione typesafe
È possibile aggiungere dati (sotto forma di campi) e comportamenti (sotto forma di metodi) a un'enumerazione typesafe. Ad esempio, supponiamo di dover introdurre una enumerazione per le monete canadesi e che questa classe debba fornire i mezzi per restituire il numero di nichelini, monetine, quarti o dollari contenuti in un numero arbitrario di centesimi. Il listato 2 mostra come eseguire questa operazione.
Listato 2: TEDemo.java
(versione 2)
enum Coin {NICKEL (5), // le costanti devono apparire prima DIME (10), QUARTER (25), DOLLAR (100); // il punto e virgola è obbligatorio private final int valueInPennies; Coin (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {return pennies / valueInPennies; }} public class TEDemo {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("usage: java TEDemo amountInPennies"); ritorno; } int pennies = Integer.parseInt (args [0]); for (int i = 0; i <Coin.values (). length; i ++) System.out.println (penny + "pennies contains" + Coin.values () [i] .toCoins (penny) + "" + Coin .values () [i] .toString (). toLowerCase () + "s"); }}
Il listato 2 prima dichiara un Coin
enum. Un elenco di costanti parametrizzate identifica quattro tipi di monete. L'argomento passato a ciascuna costante rappresenta il numero di centesimi rappresentati dalla moneta.
L'argomento passato a ciascuna costante viene effettivamente passato al Coin(int valueInPennies)
costruttore, che salva l'argomento nel valuesInPennies
campo dell'istanza. Questa variabile è accessibile dall'interno del toCoins()
metodo di istanza. Si divide in numero di centesimi passati a toCoin()
's pennies
parametro, e questo metodo restituisce il risultato, che risulta essere il numero di monete nella denominazione monetaria descritto dalla Coin
costante.
A questo punto, hai scoperto che puoi dichiarare campi di istanza, costruttori e metodi di istanza in un'enumerazione typesafe. Dopo tutto, un'enumerazione typesafe è essenzialmente un tipo speciale di classe Java.
Il metodo TEDemo
della classe main()
verifica prima che sia stato specificato un singolo argomento della riga di comando. Questo argomento viene convertito in un numero intero chiamando il metodo java.lang.Integer
della classe parseInt()
, che analizza il valore del suo argomento stringa in un numero intero (o genera un'eccezione quando viene rilevato un input non valido). Avrò altro da dire su Integer
e sulle sue classi cugine in un futuro articolo su Java 101 .
Andando avanti, main()
itera sulle Coin
costanti di. Poiché queste costanti sono archiviate in una Coin[]
matrice, main()
valuta Coin.values().length
per determinare la lunghezza di questa matrice. Per ogni iterazione dell'indice del ciclo i
, main()
valuta Coin.values()[i]
l'accesso alla Coin
costante. Invoca ciascuno di toCoins()
e toString()
su questa costante, il che dimostra ulteriormente che Coin
è un tipo speciale di classe.
Compilare il codice sorgente come segue:
javac TEDemo.java
Eseguire l'applicazione compilata come segue:
java TEDemo 198
Dovresti osservare il seguente output:
198 penny contiene 39 nickel 198 penny contiene 19 monetine 198 penny contengono 7 quarti 198 penny contengono 1 dollaro
Esplorando la classeEnum
Il compilatore Java considera enum
lo zucchero sintattico. Quando incontra una dichiarazione enum typesafe, genera una classe il cui nome è specificato dalla dichiarazione. Questa classe è una sottoclasse della classe astratta , che funge da classe base per tutte le enumerazioni typesafe.Enum
Enum
L'elenco dei parametri del tipo formale sembra orribile, ma non è così difficile da capire. Ad esempio, nel contesto di Coin extends Enum
, interpreteresti questo elenco di parametri di tipo formale come segue:
- Qualsiasi sottoclasse di
Enum
deve fornire un argomento di tipo effettivo aEnum
. Ad esempio,Coin
specifica l'intestazione diEnum
. - L'argomento del tipo effettivo deve essere una sottoclasse di
Enum
. Ad esempio,Coin
è una sottoclasse diEnum
. - A subclass of
Enum
(such asCoin
) must follow the idiom that it supplies its own name (Coin
) as an actual type argument.
Examine Enum
’s Java documentation and you’ll discover that it overrides java.lang.Object
's clone()
, equals()
, finalize()
, hashCode()
, and toString()
methods. Except for toString()
, all of these overriding methods are declared final
so that they cannot be overridden in a subclass:
clone()
is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via==
and!=
.equals()
viene sovrascritto per confrontare le costanti tramite i loro riferimenti. Costanti con le stesse identità (==
) devono avere gli stessi contenuti (equals()
) e identità differenti implicano contenuti differenti.finalize()
viene sovrascritto per garantire che le costanti non possano essere finalizzate.hashCode()
è sovrascritto perchéequals()
è sovrascritto.toString()
viene sovrascritto per restituire il nome della costante.
Enum
fornisce anche i propri metodi. Questi metodi includono i final
compareTo()
( Enum
implementa l' java.lang.Comparable
interfaccia), getDeclaringClass()
, name()
, e ordinal()
metodi: