Kako koristiti typesafe enume u Javi

Java kôd koji koristi tradicionalne nabrojane vrste je problematičan. Java 5 nam je pružila bolju alternativu u obliku typesafe enuma. U ovom članku uvodim vas u pobrojane tipove i typesafe enume, pokazat ću vam kako deklarirati typesafe enum i koristiti ga u naredbi switch, te raspraviti o prilagodbi typesafe enum dodavanjem podataka i ponašanja. Završavam članak istražujući razred.java.lang.Enum

preuzimanje Preuzmite kod Preuzmite izvorni kod za primjere u ovom vodiču za Java 101. Stvorio Jeff Friesen za JavaWorld /.

Od pobrojanih tipova do sigurnih nabrajanja

Nabrojane vrste određuje skup povezanih konstanti kao svoje vrijednosti. Primjeri uključuju tjedan dana, standardne upute kompasa sjever / jug / istok / zapad, apoene kovanica valute i vrste žetona leksičkog analizatora.

Nabrojani tipovi tradicionalno su implementirani kao nizovi cjelobrojnih konstanti, što pokazuje sljedeći skup konstanti smjera:

statički konačni int DIR_NORTH = 0; statički konačni int DIR_WEST = 1; statički konačni int DIR_EAST = 2; statički konačni int DIR_SOUTH = 3;

Postoji nekoliko problema s ovim pristupom:

  • Nedostatak sigurnosti tipa: Budući da je nabrojana konstanta tipa samo cijeli broj, može se navesti bilo koja cjelobrojna vrijednost tamo gdje je konstanta potrebna. Nadalje, zbrajanje, oduzimanje i druge matematičke operacije mogu se izvoditi na tim konstantama; na primjer, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), što je besmisleno.
  • Prostor imena nije prisutan: Konstante nabrojanog tipa moraju imati prefiks s nekom vrstom (nadamo se) jedinstvenog identifikatora (npr. DIR_) Kako bi se spriječile kolizije s konstantama drugog nabrojanog tipa.
  • Krhkost: budući da se nabrojane konstante tipa kompajliraju u datoteke klase gdje su pohranjene njihove literalne vrijednosti (u konstantnim spremištima), promjena vrijednosti konstante zahtijeva da se ove datoteke klase i one datoteke klase aplikacija koje o njima ovise obnove. U suprotnom će se tijekom izvođenja pojaviti nedefinirano ponašanje.
  • Nedostatak informacija: Kada se konstanta ispiše, izlazi njezina cijela vrijednost. Ovaj izlaz ne govori vam ništa o tome što predstavlja cjelobrojna vrijednost. Čak ni ne identificira nabrojani tip kojem konstanta pripada.

Pomoću java.lang.Stringkonstanti mogli biste izbjeći probleme s "nedostatkom sigurnosti tipa" i "nedostatkom informacija" . Na primjer, možete navesti static final String DIR_NORTH = "NORTH";. Iako je konstantna vrijednost značajnija, Stringkonstante na temelju i dalje pate od problema s "prostorom imena nije prisutan" i problema lomljivosti. Također, za razliku integer usporedbe, ne možete usporediti string vrijednosti s ==i !=operatora (što je samo usporediti reference).

Ti su problemi uzrokovali da programeri izmisle alternativu temeljenu na klasi poznatu kao Typesafe Enum . Ovaj je obrazac široko opisivan i kritiziran. Joshua Bloch predstavio je obrazac u točki 21. svog Učinkovitog vodiča za programski jezik Java (Addison-Wesley, 2001.) i primijetio da on ima nekih problema; naime da je nezgodno agregirati konstante sigurnog tipa u skupove i da se konstante nabrajanja ne mogu koristiti u switchizrazima.

Razmotrite sljedeći primjer uzorka nabrajanja typesafe. Predavanje Suitpokazuje kako biste mogli upotrijebiti alternativu koja se temelji na nastavi kako biste uveli nabrojani tip koji opisuje četiri boje karata (palice, dijamanti, srca i pikovi):

javna završna klasa odijelo // Ne bi trebalo biti u mogućnosti podrazred odijelo {javno statično završno odijelo KLUBOVI = novo odijelo (); javno statično završno odijelo DIJAMANTI = novo odijelo (); javno statično završno odijelo SRCA = novo odijelo (); javno statično završno odijelo PAD = novo odijelo (); private Suit () {} // Ne bi trebao moći uvesti dodatne konstante. }

Da biste koristili ovu klasu, trebali biste uvesti Suitvarijablu i dodijeliti je jednoj od Suitkonstanti, kako slijedi:

Odijelo odijelo = Odijelo.DIJAMANTI;

Možda ćete tada žele ispitati suitu switchizjavi poput ove:

prekidač (odijelo) {case Suit.CLUBS: System.out.println ("klubovi"); pauza; slučaj Suit.DIAMONDS: System.out.println ("dijamanti"); pauza; slučaj Suit.HEARTS: System.out.println ("srca"); pauza; slučaj Suit.SPADES: System.out.println ("pikovi"); }

Međutim, kada naiđe Java prevodilac Suit.CLUBS, prijavljuje pogrešku navodeći da je potreban konstantan izraz. Možete pokušati riješiti problem na sljedeći način:

prekidač (odijelo) {case KLUBOVI: System.out.println ("klubovi"); pauza; futrola DIJAMANTI: System.out.println ("dijamanti"); pauza; slučaj SRCA: System.out.println ("srca"); pauza; case SPADES: System.out.println ("pikovi"); }

Međutim, kad se sastavljač susretne CLUBS, prijavit će pogrešku navodeći da nije uspio pronaći simbol. Čak i ako ste stavili Suitu paket, uvezli paket i statički uvezli ove konstante, sastavljač bi se žalio da se ne može pretvoriti Suitu intkada naiđe suitu switch(suit). Što se tiče svakog case, sastavljač bi također izvijestio da je potreban konstantan izraz.

Java ne podržava Typesafe Enum obrazac s switchizrazima. Međutim, uveo je značajku typesafe enum jezika kako bi obuhvatio blagodati uzorka tijekom rješavanja njegovih problema, a ova značajka podržava switch.

Deklariranje tipičnog nabrajanja i njegovo korištenje u naredbi switch

Jednostavna deklaracija tipičnog nabrajanja u Java kodu izgleda poput svojih kopija u jezicima C, C ++ i C #:

enum Smjer {SJEVER, ZAPAD, ISTOK, JUG}

Ova deklaracija koristi ključnu riječ enumza uvođenje Directionkao typesafe enum (posebna vrsta klase), u koju se mogu dodati proizvoljne metode i implementirati proizvoljna sučelja. NORTH, WEST, EAST, I SOUTHenum konstante se provodi kao stalnim specifične klase tijela koje definiraju anonimne klase protežu priključnog Directionklase.

Directioni druge typesafe enums proširiti  i naslijediti različitih metoda, uključujući , i iz ove klase. Istražit ćemo kasnije u ovom članku.Enum values()toString()compareTo()Enum

Popis 1 deklarira gore navedeni nabrajanje i koristi ga u switchizjavi. Također pokazuje kako usporediti dvije enum konstante kako bi se utvrdilo koja konstanta dolazi prije druge konstante.

Popis 1: TEDemo.java(verzija 1)

javna klasa TEDemo {enum Smjer {SJEVER, ZAPAD, ISTOK, JUG} javna statička praznina main (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .vrijednosti () [i]; System.out.println (d); switch (d) {case NORTH: System.out.println ("Pomakni se prema sjeveru"); pauza; slučaj ZAPAD: System.out.println ("Pomakni se prema zapadu"); pauza; slučaj EAST: System.out.println ("Pomakni se prema istoku"); pauza; slučaj JUŽNO: System.out.println ("Pomakni se prema jugu"); pauza; zadana postavka: assert false: "nepoznati smjer"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Popis 1 deklarira Directiontypesafe enum i ponavlja se preko njegovih stalnih članova, što se values()vraća. Za svaku vrijednost switchizraz (poboljšan da podržava sigurne tipove nabrajanja) odabire ono casešto odgovara vrijednosti  d i daje odgovarajuću poruku. (Ne stavljate prefiksu enum konstante, npr. NORTH, S njezinim tipom enum.) I na kraju, popis 1 procjenjuje Direction.NORTH.compareTo(Direction.SOUTH)kako bi utvrdio NORTHdolazi li prije SOUTH.

Sastavite izvorni kod na sljedeći način:

javac TEDemo.java

Pokrenite kompajliranu aplikaciju na sljedeći način:

java TEDemo

Trebali biste promatrati sljedeći rezultat:

SJEVER Pomaknite se na sjever ZAPAD Pomaknite se prema zapadu ISTOK Pomaknite se prema istoku JUŽNO Pomaknite se prema jugu -3

Izlaz otkriva da naslijeđena toString()metoda vraća ime enumske konstante, a to NORTHdolazi prije SOUTHu usporedbi ovih enumskih konstanti.

Dodavanje podataka i ponašanja sigurnom nabrajanju

Možete dodati podatke (u obliku polja) i ponašanja (u obliku metoda) u sigurni popis. Na primjer, pretpostavimo da trebate uvesti nabrajanje za kanadske kovanice i da ova klasa mora osigurati način za vraćanje broja nikla, novčića, četvrtina ili dolara koji se sadrže proizvoljni broj groša. Popis 2 pokazuje vam kako izvršiti ovaj zadatak.

Popis 2: TEDemo.java(verzija 2)

enum Coin {NICKEL (5), // konstante se moraju pojaviti prvo DIME (10), QUARTER (25), DOLLAR (100); // točka-zarez je obavezna private final int valueInPennies; Novčić (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {return pennies / valueInPennies; }} javna klasa TEDemo {javna statička void glavna (Niz [] args) {if (args.length! = 1) {System.err.println ("upotreba: java TEDemo iznosInPennies"); povratak; } int peni = Integer.parseInt (args [0]); for (int i = 0; i <Coin.values ​​(). length; i ++) System.out.println (peni + "peni sadrži" + Coin.values ​​() [i] .toCoins (pennies) + "" + Coin .values ​​() [i] .toString (). toLowerCase () + "s"); }}

Popis 2 prvo objavljuje Coinnabrajanje. Popis parametriziranih konstanti identificira četiri vrste kovanica. Argument prosljeđen svakoj konstanti predstavlja broj groša koje predstavlja novčić.

Argument prosljeđen svakoj konstanti zapravo se prenosi Coin(int valueInPennies)konstruktoru koji sprema argument u valuesInPenniespolje instance. Ovoj se varijabli pristupa iz toCoins()metode instance. Dijeli se na broj penija prosljeđenih parametru toCoin()'s pennies, a ova metoda vraća rezultat, a to je broj novčića u novčanom apoenu koji opisuje Coinkonstanta.

U ovom trenutku otkrili ste da možete deklarirati polja instance, konstruktore i metode instance u sigurnom nabrajanju. Napokon, typesafe enum u osnovi je posebna vrsta Java klase.

Metoda TEDemoklase main()prvo provjerava je li naveden jedan argument naredbenog retka. Ovaj se argument pretvara u cijeli broj pozivanjem metode java.lang.Integerklase parseInt()koja analizira vrijednost svog argumenta niza u cijeli broj (ili izuzima iznimku kada se otkrije nevaljani unos). U Integerbudućem članku o Javi 101 reći ću više o njezinim razredima rođaka .

Moving forward, main() iterates over Coin’s constants. Because these constants are stored in a Coin[] array, main() evaluates Coin.values().length to determine the length of this array. For each iteration of loop index i, main() evaluates Coin.values()[i] to access the Coin constant. It invokes each of toCoins() and toString() on this constant, which further proves that Coin is a special kind of class.

Compile the source code as follows:

javac TEDemo.java

Run the compiled application as follows:

java TEDemo 198

You should observe the following output:

198 pennies contains 39 nickels 198 pennies contains 19 dimes 198 pennies contains 7 quarters 198 pennies contains 1 dollars

Exploring the Enum class

The Java compiler considers enum to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum class, which serves as the base class for all typesafe enums.

Enum’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum, you would interpret this formal type parameter list as follows:

  • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of 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()je poništeno za usporedbu konstanti putem njihovih referenci. Konstante s istim identitetima ( ==) moraju imati isti sadržaj ( equals()), a različiti identiteti podrazumijevaju različite sadržaje.
  • finalize() je nadjačana kako bi se osiguralo da konstante ne mogu biti finalizirane.
  • hashCode()je nadjačana jer equals()je nadjačana.
  • toString() je poništeno za vraćanje imena konstante.

Enumtakođer pruža vlastite metode. Ove metode uključuju finalcompareTo() ( Enumimplementira java.lang.Comparablesučelje), getDeclaringClass(), name()i ordinal()metode: