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

download Ottieni il codice Scarica il codice sorgente per esempi in questo tutorial di Java 101. Creato da Jeff Friesen per JavaWorld /.

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.Stringcostanti. Ad esempio, potresti specificare static final String DIR_NORTH = "NORTH";. Sebbene il valore della costante sia più significativo, le Stringcostanti 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 switchistruzioni.

Considera il seguente esempio del modello enumerazione typesafe. La Suitclasse 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 Suitvariabile e assegnarla a una delle Suitcostanti di, come segue:

Suit suit = Suit.DIAMONDS;

Potresti quindi voler interrogare suitin una switchdichiarazione 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.CLUBSrileva, 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 Suitin un pacchetto, si importa il pacchetto e si importano staticamente queste costanti, il compilatore si lamenta di non poter convertire Suita intquando incontra suitin 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 switchistruzioni. 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 enumper introdurre Directioncome enum typesafe (un tipo speciale di classe), in cui è possibile aggiungere metodi arbitrari e implementare interfacce arbitrarie. La NORTH, WEST, EAST, e SOUTHcostanti enum sono implementati come corpi classe specifica costanti che definiscono le classi anonime estendentisi delle allegando Directionclasse.

Directione 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 switchun'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' Directionenumerazione typesafe e itera sui suoi membri costanti, che values()restituisce. Per ogni valore, l' switchistruzione (migliorata per supportare enumerazioni typesafe) sceglie il caseche corrisponde al valore di  d e restituisce un messaggio appropriato. (Non si antepone a una costante enum, ad esempio, il NORTHsuo tipo enum.) Infine, il Listato 1 valuta Direction.NORTH.compareTo(Direction.SOUTH)per determinare se NORTHviene 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 NORTHviene prima SOUTHin 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 Coinenum. 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 valuesInPenniescampo dell'istanza. Questa variabile è accessibile dall'interno del toCoins()metodo di istanza. Si divide in numero di centesimi passati a toCoin()'s penniesparametro, e questo metodo restituisce il risultato, che risulta essere il numero di monete nella denominazione monetaria descritto dalla Coincostante.

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 TEDemodella 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.Integerdella 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 Integere sulle sue classi cugine in un futuro articolo su Java 101 .

Andando avanti, main()itera sulle Coincostanti di. Poiché queste costanti sono archiviate in una Coin[]matrice, main()valuta Coin.values().lengthper determinare la lunghezza di questa matrice. Per ogni iterazione dell'indice del ciclo i, main()valuta Coin.values()[i]l'accesso alla Coincostante. 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 enumlo 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

EnumL'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 Enumdeve fornire un argomento di tipo effettivo a Enum. Ad esempio, Coinspecifica l'intestazione di Enum.
  • L'argomento del tipo effettivo deve essere una sottoclasse di Enum. Ad esempio, Coinè una sottoclasse di Enum.
  • A subclass of Enum (such as Coin) 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.

Enumfornisce anche i propri metodi. Questi metodi includono i finalcompareTo() ( Enumimplementa l' java.lang.Comparableinterfaccia), getDeclaringClass(), name(), e ordinal()metodi: