Crea costanti enumerate in Java

Un insieme di "costanti enumerabili" è un insieme ordinato di costanti che possono essere contate, come i numeri. Questa proprietà ti consente di usarli come numeri per indicizzare un array, oppure puoi usarli come variabile di indice in un ciclo for. In Java, tali oggetti sono spesso noti come "costanti enumerate".

L'utilizzo di costanti enumerate può rendere il codice più leggibile. Ad esempio, potresti voler definire un nuovo tipo di dati denominato Colore con le costanti ROSSO, VERDE e BLU come possibili valori. L'idea è di avere Colore come attributo di altri oggetti che crei, come gli oggetti Auto:

classe Auto {Colore colore; ...}

Quindi puoi scrivere codice chiaro e leggibile, come questo:

 myCar.color = ROSSO; 

invece di qualcosa di simile:

 myCar.color = 3; 

Un attributo ancora più importante delle costanti enumerate in linguaggi come Pascal è che sono indipendenti dai tipi. In altre parole, non è possibile assegnare un colore non valido all'attributo color: deve essere sempre ROSSO, VERDE o BLU. Al contrario, se la variabile color fosse un int, potresti assegnarle un numero intero valido, anche se quel numero non rappresenta un colore valido.

Questo articolo fornisce un modello per la creazione di costanti enumerate che sono:

  • Digita safe
  • Stampabile
  • Ordinato, da utilizzare come indice
  • Collegato, per il loop in avanti o all'indietro
  • Enumerabile

In un prossimo articolo imparerai come estendere le costanti enumerate per implementare il comportamento dipendente dallo stato.

Perché non usare le finali statiche?

Un meccanismo comune per le costanti enumerate utilizza variabili int finali statiche, come questa:

statica finale int RED = 0; finale statico int VERDE = 1; int finale statico BLU = 2; ...

Le finali statiche sono utili

Poiché sono definitivi, i valori sono costanti e immutabili. Poiché sono statici, vengono creati solo una volta per la classe o l'interfaccia in cui sono definiti, invece di una volta per ogni oggetto. E poiché sono variabili intere, possono essere enumerate e utilizzate come indice.

Ad esempio, puoi scrivere un ciclo per creare un elenco dei colori preferiti di un cliente:

for (int i = 0; ...) {if (customerLikesColor (i)) {favoriteColors.add (i); }}

Puoi anche indicizzare in un array o un vettore utilizzando le variabili per ottenere un valore associato al colore. Ad esempio, supponi di avere un gioco da tavolo che ha pezzi colorati diversi per ogni giocatore. Supponiamo che tu abbia una bitmap per ogni parte di colore e un metodo chiamato display()che copia quella bitmap nella posizione corrente. Un modo per mettere un pezzo sulla scacchiera potrebbe essere qualcosa del genere:

PiecePicture redPiece = nuovo PiecePicture (RED); PiecePicture greenPiece = nuovo PiecePicture (VERDE); PiecePicture bluePiece = nuovo PiecePicture (BLU);

void placePiece (int location, int color) {setPosition (location); if (color == RED) {display (redPiece); } else if (color == GREEN) {display (greenPiece); } else {display (bluePiece); }}

Ma usando i valori interi per indicizzare in una matrice di pezzi, puoi semplificare il codice per:

PiecePicture [] piece = {new PiecePicture (RED), new PiecePicture (GREEN), new PiecePicture (BLUE)}; void placePiece (int location, int color) {setPosition (location); display (pezzo [colore]); }

Essere in grado di eseguire il ciclo su un intervallo di costanti e indicizzare in un array o vettore sono i principali vantaggi degli interi finali statici. E quando il numero di scelte cresce, l'effetto di semplificazione è ancora maggiore.

Ma le finali statiche sono rischiose

Tuttavia, ci sono un paio di svantaggi nell'utilizzo di interi finali statici. Il principale svantaggio è la mancanza di sicurezza dei tipi. Qualsiasi numero intero calcolato o letto può essere utilizzato come "colore", indipendentemente dal fatto che abbia senso farlo. È possibile eseguire il ciclo fino alla fine delle costanti definite o interrompere prima di coprirle tutte, il che può facilmente accadere se si aggiunge o si rimuove una costante dall'elenco ma si dimentica di regolare l'indice del ciclo.

Ad esempio, il ciclo delle preferenze di colore potrebbe essere simile a questo:

for (int i = 0; i <= BLUE; i ++) {if (customerLikesColor (i)) {favoriteColors.add (i); }}

Successivamente, potresti aggiungere un nuovo colore:

statica finale int RED = 0; finale statico int VERDE = 1; int finale statico BLU = 2; int finale statico MAGENTA = 3;

Oppure potresti rimuoverne uno:

statica finale int RED = 0; int finale statico BLU = 1;

In entrambi i casi, il programma non funzionerà correttamente. Se rimuovi un colore, otterrai un errore di runtime che attira l'attenzione sul problema. Se aggiungi un colore, non otterrai alcun errore: il programma semplicemente non riuscirà a coprire tutte le scelte di colore.

Un altro svantaggio è la mancanza di un identificatore leggibile. Se si utilizza una finestra di messaggio o l'output della console per visualizzare la scelta del colore corrente, si ottiene un numero. Ciò rende il debug piuttosto difficile.

I problemi che creano un identificatore leggibile a volte vengono risolti utilizzando costanti di stringa finale statiche, come questa:

statica finale String RED = "red" .intern (); ...

L'utilizzo del intern()metodo garantisce che nel pool di stringhe interno sia presente una sola stringa con quei contenuti. Ma per intern()essere efficace, ogni stringa o variabile di stringa che viene mai confrontata con RED deve usarla. Anche in questo caso, le stringhe finali statiche non consentono il ciclo o l'indicizzazione in un array e non risolvono il problema dell'indipendenza dai tipi.

Tipo di sicurezza

Il problema con gli interi finali statici è che le variabili che li utilizzano sono intrinsecamente illimitate. Sono variabili int, il che significa che possono contenere qualsiasi numero intero, non solo le costanti che avrebbero dovuto contenere. L'obiettivo è definire una variabile di tipo Color in modo da ottenere un errore di compilazione anziché un errore di runtime ogni volta che a quella variabile viene assegnato un valore non valido.

Un'elegante soluzione è stata fornita nell'articolo di Philip Bishop in JavaWorld, "Costanti Typesafe in C ++ e Java".

L'idea è davvero semplice (una volta che la vedi!):

public final class Color {// final class !! private Color () {} // costruttore privato !!

pubblico statico finale Colore ROSSO = nuovo Colore (); pubblico statico finale Colore VERDE = nuovo Colore (); pubblico statico finale Colore BLU = nuovo Colore (); }

Because the class is defined as final, it can't be subclassed. No other classes will be created from it. Because the constructor is private, other methods can't use the class to create new objects. The only objects that will ever be created with this class are the static objects the class creates for itself the first time the class is referenced! This implementation is a variation of the Singleton pattern that limits the class to a predefined number of instances. You can use this pattern to create exactly one class any time you need a Singleton, or use it as shown here to create a fixed number of instances. (The Singleton pattern is defined in the book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides, Addison-Wesley, 1995. See the Resources section for a link to this book.)

The mind-boggling part of this class definition is that the class uses itself to create new objects. The first time you reference RED, it doesn't exist. But the act of accessing the class that RED is defined in causes it to be created, along with the other constants. Admittedly, that kind of recursive reference is rather difficult to visualize. But the advantage is total type safety. A variable of type Color can never be assigned anything other than the RED, GREEN, or BLUE objects that the Color class creates.

Identifiers

The first enhancement to the typesafe enumerated constant class is to create a string representation of the constants. You want to be able to produce a readable version of the value with a line like this:

 System.out.println(myColor); 

Whenever you output an object to a character output stream like System.out, and whenever you concatenate an object to a string, Java automatically invokes the toString() method for that object. That's a good reason to define a toString() method for any new class you create.

If the class does not have a toString() method, the inheritance hierarchy is inspected until one is found. At the top of the hierarchy, the toString() method in the Object class returns the class name. So the toString() method always has some meaning, but most of the time the default method will not be very useful.

Here is a modification to the Color class that provides a useful toString() method:

public final class Color { private String id; private Color(String anID) {this.id = anID; } public String toString() {return this.id; }

public static final Color RED = new Color(

"Red"

); public static final Color GREEN = new Color(

"Green"

); public static final Color BLUE = new Color(

"Blue"

); }

This version adds a private String variable (id). The constructor has been modified to take a String argument and store it as the object's ID. The toString() method then returns the object's ID.

One trick you can use to invoke the toString() method takes advantage of the fact that it is automatically invoked when an object is concatenated to a string. That means you could put the object's name in a dialog by concatenating it to a null string using a line like the following:

 textField1.setText("" + myColor); 

Unless you happen to love all the parentheses in Lisp, you will find that a bit more readable than the alternative:

 textField1.setText(myColor.toString()); 

It's also easier to make sure you put in the right number of closing parentheses!

Ordering and indexing

The next question is how to index into a vector or an array using members of the

Color

class. The mechanism will be to assign an ordinal number to each class constant and reference it using the attribute

.ord

, like this:

 void placePiece(int location, int color) { setPosition(location); display(piece[color.ord]); } 

Although tacking on .ord to convert the reference to color into a number is not particularly pretty, it is not terribly obtrusive either. It seems like a fairly reasonable tradeoff for typesafe constants.

Here is how the ordinal numbers are assigned:

public final class Color { private String id; public final int ord;private static int upperBound = 0; private Color(String anID) { this.id = anID; this.ord = upperBound++; } public String toString() {return this.id; } public static int size() { return upperBound; }

public static final Color RED = new Color("Red"); public static final Color GREEN = new Color("Green"); public static final Color BLUE = new Color("Blue"); }

This code uses the new JDK version 1.1 definition of a "blank final" variable -- a variable that is assigned a value once and once only. This mechanism allows each object to have its own non-static final variable, ord, which will be assigned once during object creation and which will thereafter remain immutable. The static variable upperBound keeps track of the next unused index in the collection. That value becomes the ord attribute when the object is created, after which the upper bound is incremented.

For compatibility with the Vector class, the method size() is defined to return the number of constants that have been defined in this class (which is the same as the upper bound).

A purist might decide that the variable ord should be private, and the method named ord() should return it -- if not, a method named getOrd(). I lean toward accessing the attribute directly, though, for two reasons. The first is that the concept of an ordinal is unequivocally that of an int. There is little likelihood, if any, that the implementation would ever change. The second reason is that what you really want is the ability to use the object as though it were an int, as you could in a language like Pascal. For example, you might want to use the attribute color to index an array. But you cannot use a Java object to do that directly. What you would really like to say is:

 display(piece[color]); // desirable, but does not work 

But you can't do that. The minimum change necessary to get what you want is to access an attribute, instead, like this:

 display(piece[color.ord]); // closest to desirable 

instead of the lengthy alternative:

 display(piece[color.ord()]); // extra parentheses 

or the even lengthier:

 display(piece[color.getOrd()]); // extra parentheses and text 

The Eiffel language uses the same syntax for accessing attributes and invoking methods. That would be the ideal. Given the necessity of choosing one or the other, however, I've gone with accessing ord as an attribute. With any luck, the identifier ord will become so familiar as a result of repetition that using it will seem as natural as writing int. (As natural as that may be.)

Looping

Il passo successivo è essere in grado di iterare sulle costanti della classe. Vuoi essere in grado di ripetere dall'inizio alla fine:

 for (Color c = Color.first (); c! = null; c = c.next ()) {...} 

o dalla fine all'inizio:

 for (Color c = Color.last (); c! = null; c = c.prev ()) {...} 

Queste modifiche utilizzano variabili statiche per tenere traccia dell'ultimo oggetto creato e collegarlo all'oggetto successivo: