Stvorite nabrojane konstante u Javi

Skup "nabrojivih konstanti" uređena je zbirka konstanti koje se mogu brojati, poput brojeva. To svojstvo omogućuje vam da ih koristite poput brojeva za indeksiranje niza ili ih možete koristiti kao indeksnu varijablu u for petlji. U Javi su takvi objekti najčešće poznati kao "nabrojane konstante".

Korištenje nabrojanih konstanti može učiniti kôd čitljivijim. Na primjer, možda ćete htjeti definirati novi tip podataka pod nazivom Boja s konstantama CRVENA, ZELENA i PLAVA kao moguće vrijednosti. Ideja je imati boju kao atribut ostalih objekata koje stvarate, poput objekata automobila:

razred Automobil {Boja u boji; ...}

Tada možete napisati jasan, čitljiv kod, poput ovog:

 myCar.color = CRVENA; 

umjesto nečega poput:

 myCar.color = 3; 

Još važniji atribut nabrojanih konstanti u jezicima poput Pascala jest da su oni sigurni za tip. Drugim riječima, atributu boje nije moguće dodijeliti nevaljanu boju - ona uvijek mora biti CRVENA, ZELENA ili PLAVA. Suprotno tome, ako je varijabla boje int, tada biste joj mogli dodijeliti bilo koji valjani cijeli broj, čak i ako taj broj nije predstavljao valjanu boju.

Ovaj vam članak daje predložak za stvaranje nabrojanih konstanti koje su:

  • Upišite sigurno
  • Za ispis
  • Naručeno, za upotrebu kao indeks
  • Povezano, za petlje prema naprijed ili unatrag
  • Brojne

U budućem ćete članku naučiti kako proširiti nabrojane konstante kako bi se primijenilo ponašanje ovisno o državi.

Zašto ne koristiti statično finale?

Uobičajeni mehanizam za nabrojane konstante koristi statičke finalne int varijable, poput ove:

statički konačni int CRVENI = 0; statički konačni int ZELENI = 1; statički konačni int PLAVI = 2; ...

Statična finala su korisna

Budući da su konačne, vrijednosti su stalne i nepromjenjive. Budući da su statični, stvaraju se samo jednom za klasu ili sučelje u kojem su definirani, umjesto jednom za svaki objekt. A budući da su cjelobrojne varijable, mogu se nabrojati i koristiti kao indeks.

Na primjer, možete napisati petlju za stvaranje popisa omiljenih boja kupca:

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

Također možete indeksirati u niz ili vektor pomoću varijabli da biste dobili vrijednost povezanu s bojom. Na primjer, pretpostavimo da imate društvenu igru ​​koja ima različite dijelove u boji za svakog igrača. Recimo da imate bitmapu za svaki komad boje i metodu koja se zove display()koja kopira tu bitmapu na trenutno mjesto. Jedan od načina da stavite komad na ploču može biti otprilike ovaj:

PiecePicture redPiece = nova PiecePicture (CRVENA); PiecePicture greenPiece = nova PiecePicture (ZELENA); PiecePicture bluePiece = nova PiecePicture (PLAVA);

void placePiece (int lokacija, int boja) {setPosition (lokacija); if (boja == CRVENA) {display (redPiece); } else if (boja == ZELENA) {display (greenPiece); } else {prikaz (bluePiece); }}

No, pomoću cjelobrojnih vrijednosti za indeksiranje u niz dijelova možete kôd pojednostaviti na:

PiecePicture [] komad = {nova PiecePicture (CRVENA), nova PiecePicture (ZELENA), nova PiecePicture (PLAVA)}; void placePiece (int lokacija, int boja) {setPosition (lokacija); zaslon (komad [boja]); }

Mogućnost petljanja kroz niz konstanti i indeksiranje u niz ili vektor glavne su prednosti statičkih završnih cijelih brojeva. A kad broj izbora raste, učinak pojednostavljenja je još veći.

No statična finala su rizična

Ipak, postoji nekoliko nedostataka korištenja statičkih završnih cijelih brojeva. Glavni nedostatak je nedostatak sigurnosti tipa. Bilo koji cijeli broj koji se izračuna ili pročita može se koristiti kao "boja", bez obzira na to ima li smisla to učiniti. Možete proći petlju kraj kraja definiranih konstanti ili zaustaviti da ih sve ne pokrijete, što se lako može dogoditi ako dodate ili uklonite konstantu s popisa, ali zaboravite prilagoditi indeks petlje.

Na primjer, vaša petlja za odabir boje može čitati ovako:

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

Kasnije biste mogli dodati novu boju:

statički konačni int CRVENI = 0; statički konačni int ZELENI = 1; statički konačni int PLAVI = 2; statički konačni int MAGENTA = 3;

Ili možete ukloniti jedan:

statički konačni int CRVENI = 0; statički konačni int PLAVI = 1;

U oba slučaja program neće raditi ispravno. Ako uklonite boju, dobit ćete pogrešku u izvođenju koja skreće pozornost na problem. Ako dodate boju, uopće nećete dobiti pogrešku - program jednostavno neće uspjeti pokriti sve izbore boja.

Još jedan nedostatak je nedostatak čitljivog identifikatora. Ako upotrijebite okvir za poruke ili izlaz konzole za prikaz trenutnog izbora boja, dobit ćete broj. To otklanjanje pogrešaka čini prilično teškim.

Problemi sa stvaranjem čitljivog identifikatora ponekad se rješavaju pomoću statičkih konačnih konstanti niza, poput ove:

statički završni niz CRVENO = "crveno" .intern (); ...

Korištenje intern()metode jamči da postoji samo jedan niz s tim sadržajem u unutarnjem spremištu nizova. Ali da intern()bi bili učinkoviti, svaki niz ili varijabla niza koja se ikad uspoređuje s CRVENOM mora ih koristiti. Čak i tada, statički završni nizovi ne dopuštaju petlje niti indeksiranje u niz i još uvijek ne rješavaju pitanje sigurnosti tipa.

Sigurnost tipa

Problem sa statičkim završnim cijelim brojevima je taj što su varijable koje ih koriste u biti neomeđene. Oni su int varijable, što znači da mogu sadržavati bilo koju cijelu brojku, a ne samo konstante koje su trebali zadržati. Cilj je definirati varijablu tipa Color tako da dobijete pogrešku kompilacije, a ne pogrešku izvršavanja kad god je toj varijabli dodijeljena nevaljana vrijednost.

Elegantno rješenje pruženo je u članku Philipa Bishop-a u JavaWorldu, "Konstante Typesafe u C ++ i Java."

Ideja je stvarno jednostavna (kad je jednom vidite!):

javni završni razred Boja {// završni razred !! private Color () {} // privatni konstruktor !!

javna statička konačna boja CRVENA = nova boja (); javna statička završna boja ZELENA = nova boja (); javna statička konačna boja PLAVA = nova boja (); }

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

Sljedeći je korak mogućnost prevođenja konstanti klase. Želite imati mogućnost petlje od početka do kraja:

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

ili od kraja natrag do početka:

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

Ove izmjene koriste statičke varijable za praćenje zadnjeg stvorenog objekta i povezivanje sa sljedećim objektom: