Problematický je kód Java, ktorý používa tradičné vymenované typy. Java 5 nám poskytla lepšiu alternatívu v podobe typicky bezpečných enumov. V tomto článku vám predstavím vymenované typy a typy enums, ukážem vám, ako deklarovať typicky enum a použiť ich vo vyhlásení switch, a diskutujem o prispôsobení typového enum pridaním údajov a správania. Článok zhŕňam preskúmaním java.lang.Enum
trieda.
Od vymenovaných typov až po bezpečné typy
An vymenovaný typ špecifikuje množinu súvisiacich konštánt ako svoje hodnoty. Príklady zahŕňajú týždeň dní, štandardné smery kompasu sever / juh / východ / západ, nominálne hodnoty mincí meny a typy tokenov lexikálneho analyzátora.
Vymenované typy sa tradične implementovali ako sekvencie celočíselných konštánt, čo demonštruje nasledujúca sada smerových konštánt:
statický konečný int DIR_NORTH = 0; statický konečný int DIR_WEST = 1; statický konečný int DIR_EAST = 2; statický konečný int DIR_SOUTH = 3;
S týmto prístupom sa spája niekoľko problémov:
- Nedostatok bezpečnosti typu: Pretože konštanta vymenovaného typu je iba celé číslo, je možné zadať akékoľvek celé číslo tam, kde je konštanta požadovaná. Ďalej je možné s týmito konštantami vykonať sčítanie, odčítanie a ďalšie matematické operácie; napríklad,
(DIR_NORTH + DIR_EAST) / DIR_SOUTH
), čo je nezmyselné. - Menný priestor nie je prítomný: Pred konštanty vymenovaného typu musí byť predpona s akýmsi (dúfajme) jedinečným identifikátorom (napr.
DIR_
), aby sa zabránilo kolíziám s konštantami iného vymenovaného typu. - Krehkosť: Pretože vymenované konštanty typu sa kompilujú do súborov triedy, kde sú uložené ich literálne hodnoty (v konštantných fondoch), zmena hodnoty konštanty vyžaduje, aby boli znova zostavené tieto súbory triedy a súbory triedy aplikácií, ktoré na nich závisia. V opačnom prípade dôjde počas behu k nedefinovanému správaniu.
- Nedostatok informácií: Po vytlačení konštanty sa vygeneruje jej celočíselná hodnota. Tento výstup vám nehovorí nič o tom, čo predstavuje celočíselná hodnota. Neidentifikuje ani vymenovaný typ, ku ktorému konštanta patrí.
Použitiu by ste sa mohli vyhnúť problémom „nedostatku bezpečnosti typu“ a „nedostatku informácií“ java.lang.String
konštanty. Môžete napríklad určiť statický konečný reťazec DIR_NORTH = "SEVER";
. Aj keď je konštantná hodnota zmysluplnejšia, String
konštanty založené na stále trpia problémami s „priestorom mien nie je prítomný“ a krehkosťou. Na rozdiel od celočíselných porovnaní tiež nemôžete porovnávať hodnoty reťazcov s ==
a !=
operátormi (ktorí porovnávajú iba referencie).
Tieto problémy spôsobili, že vývojári vymysleli alternatívu založenú na triedach známu ako Typický bezpečný výčet. Tento vzorec bol široko opísaný a kritizovaný. Joshua Bloch uviedol vzor v svojej položke 21 Sprievodca efektívnym programovacím jazykom Java (Addison-Wesley, 2001) a poznamenal, že má určité problémy; a to konkrétne, že je nepríjemné agregovať konštanty výčtu typov s bezpečnosťou do množín a že konštanty výčtu nie je možné použiť v prepínač
Vyhlásenia.
Zvážte nasledujúci príklad vzorca bezpečného výčtu. The Oblek
class ukazuje, ako môžete použiť alternatívu založenú na triede na zavedenie vymenovaného typu, ktorý popisuje štyri kombinácie kariet (palice, diamanty, srdcia a piky):
public final class Suit // Nemalo by byť možné podtriedu Suit. {public static final Suit CLUBS = new Suit (); verejný statický konečný oblek DIAMONDS = nový oblek (); verejný statický konečný oblek SRDCE = nový oblek (); verejné statické finále Suit SPADES = nový Suit (); private Suit () {} // Nemalo by byť možné zaviesť ďalšie konštanty. }
Ak chcete použiť túto triedu, zaviedli by ste a Oblek
premennú a priraďte ju k jednému z Oblek
Konštanty:
Oblek = Suit.DIAMONDS;
Možno budete chcieť vypočuť oblek
v prepínač
vyhlásenie ako toto:
switch (oblek) {case Suit.CLUBS: System.out.println ("kluby"); prestávka; prípad Suit.DIAMONDS: System.out.println ("diamanty"); prestávka; prípad Suit.HEARTS: System.out.println ("srdcia"); prestávka; case Suit.SPADES: System.out.println ("piky"); }
Keď sa však stretne kompilátor Java Oblek. KLUBY
, hlási chybu o tom, že je vyžadovaný konštantný výraz. Môžete skúsiť problém vyriešiť nasledovne:
switch (oblek) {case CLUBS: System.out.println ("kluby"); prestávka; prípad DIAMONDS: System.out.println ("diamanty"); prestávka; prípad SRDCE: System.out.println ("srdcia"); prestávka; prípad SPADES: System.out.println ("piky"); }
Keď však narazí na kompilátor KLUBY
, nahlási chybu s oznámením, že symbol nebol schopný nájsť. A to aj v prípade, že ste sa umiestnili Oblek
v balíku, importoval balík a staticky importoval tieto konštanty, kompilátor by sa sťažoval, že nemôže konvertovať Oblek
do int
pri stretnutí oblek
v prepínač (oblek)
. Ohľadom každého prípade
, kompilátor by tiež oznámil, že je vyžadovaný konštantný výraz.
Java nepodporuje vzor Typesafe Enum prepínač
Vyhlásenia. Zaviedla však typicky bezpečné jazyková funkcia na zapuzdrenie výhod modelu pri riešení jeho problémov a táto funkcia podporuje prepínač
.
Deklarovanie typického výčtu a jeho použitie vo vyhlásení switch
Jednoduchá deklarácia bezpečného typu v kóde Java vyzerá ako jeho náprotivky v jazykoch C, C ++ a C #:
enum Smer {SEVER, ZÁPAD, VÝCHOD, JUH}
Táto deklarácia používa kľúčové slovo enum
predstaviť Smer
ako typicky bezpečný výčet (špeciálny druh triedy), do ktorého je možné pridávať ľubovoľné metódy a implementovať ľubovoľné rozhrania. The SEVER
, ZÁPAD
, VÝCHOD
a JUH
konštanty enum sú implementované ako konštantne špecifické triedy telies, ktoré definujú anonymné triedy rozširujúce ohraničenie Smer
trieda.
Smer
a rozširujú sa ďalšie bezpečné typy Enum
a dedia rôzne metódy, vrátane hodnoty ()
, natiahnuť()
a porovnať s()
, z tejto triedy. Budeme skúmať Enum
ďalej v tomto článku.
Zoznam 1 deklaruje vyššie uvedený enum a používa ho v a prepínač
vyhlásenie. Ukazuje tiež, ako možno porovnať dve konštanty enum a určiť, ktorá konštanta má pred druhou konštantou.
Výpis 1: TEDemo.java
(verzia 1)
public class TEDemo {enum Smer {SEVER, ZÁPAD, VÝCHOD, JUH} public static void main (String [] args) {for (int i = 0; i <Direction.values (). length; i ++) {Direction d = Direction .hodnoty () [i]; System.out.println (d); switch (d) {case SEVER: System.out.println ("Presunúť na sever"); prestávka; prípad ZÁPAD: System.out.println ("Presunúť na západ"); prestávka; prípad EAST: System.out.println ("Presunúť na východ"); prestávka; prípad JUH: System.out.println ("Presunúť na juh"); prestávka; predvolené: assert false: "neznámy smer"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}
Zoznam 1 vyhlasuje Smer
typesafe enum a iteruje nad svojimi stálymi členmi, ktoré hodnoty ()
vracia. Pre každú hodnotu prepínač
vyhlásenie (vylepšené o podporu typeafe enums) zvolí prípade
ktorá zodpovedá hodnoted
a vydá príslušnú správu. (Nemáte predponu konštantu enum, napr. SEVER
s typom enum.) Nakoniec sa vyhodnotí záznam č. 1 Direction.NORTH.compareTo (Direction.SOUTH)
zistiť, či SEVER
príde skôr JUH
.
Zostavte zdrojový kód nasledovne:
javac TEDemo.java
Spustite skompilovanú aplikáciu nasledovne:
java TEDemo
Mali by ste dodržiavať nasledujúci výstup:
SEVER Presun na sever ZÁPAD Presun na západ VÝCHOD Presun na východ JUH Presun na juh -3
Výstup ukazuje, že zdedené natiahnuť()
metóda vráti názov konštanty enum a to SEVER
príde skôr JUH
v porovnaní týchto konštánt enum.
Pridávanie údajov a správania do typicky bezpečného výčtu
Do typického výčtu môžete pridať údaje (vo forme polí) a správanie (vo forme metód). Predpokladajme napríklad, že musíte zaviesť enum pre kanadské mince a táto trieda musí poskytovať prostriedky na vrátenie počtu niklov, desetníkov, štvrtí alebo dolárov obsiahnutých v ľubovoľnom počte centov. V zozname 2 je uvedené, ako dosiahnuť túto úlohu.
Výpis 2: TEDemo.java
(verzia 2)
enum Coin {NICKEL (5), // konštanty sa musia zobraziť najskôr DIME (10), QUARTER (25), DOLLAR (100); // bodkočiarka je povinná private final int valueInPennies; Coin (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int haliere) {return pennies / valueInPennies; }} public class TEDemo {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("usage: java TEDemo amountInPennies"); návrat; } int pennies = Integer.parseInt (args [0]); for (int i = 0; i <Coin.values (). length; i ++) System.out.println (pennies + "pennies contains" + Coin.values () [i] .toCoins (pennies) + "" + Coin .values () [i] .toString (). toLowerCase () + "s"); }}
Zoznam 2 najskôr deklaruje a Mince
enum. Zoznam parametrizovaných konštánt identifikuje štyri druhy mincí. Argument odovzdaný každej konštante predstavuje počet halierov, ktoré minca predstavuje.
Argument odovzdaný každej konštante je skutočne odovzdaný parametru Coin (int valueInPennies)
konštruktor, ktorý uloží argument do súboru valuesInPennies
inštančné pole. Táto premenná je sprístupnená zvnútra toCoins ()
inštančná metóda. Rozdeľuje sa na počet centov odovzdaných toCoin ()
‘S haliere
parameter a táto metóda vráti výsledok, ktorým sa stane počet mincí v menovej hodnote opísaný pomocou Mince
konštantný.
V tomto okamihu ste zistili, že môžete deklarovať polia inštancií, konštruktory a metódy inštancií v typicky bezpečnom výpise. Koniec koncov, typicky bezpečný výčet je v podstate zvláštny druh triedy Java.
The TEDemo
triedy hlavný()
metóda najskôr overí, či bol zadaný jeden argument príkazového riadku. Tento argument sa prevedie na celé číslo volaním znaku java.lang.Integer
triedy parseInt ()
metóda, ktorá analyzuje hodnotu svojho reťazcového argumentu na celé číslo (alebo vyvolá výnimku, keď sa zistí neplatný vstup). K tomu budem musieť povedať viac Celé číslo
a kurzy jeho bratranca v budúcnosti Java 101 článok.
Hýbať sa vpred, hlavný()
opakuje sa Mince
Konštanty. Pretože tieto konštanty sú uložené v a Mince []
pole, hlavný()
hodnotí Coin.values (). Dĺžka
na určenie dĺžky tohto poľa. Pre každú iteráciu indexu slučky i
, hlavný()
hodnotí Coin.values () [i]
pre prístup k Mince
konštantný. Vyvoláva každú z nich toCoins ()
a natiahnuť()
na tejto konštante, čo to ďalej dokazuje Mince
je zvláštny druh triedy.
Zostavte zdrojový kód nasledovne:
javac TEDemo.java
Spustite skompilovanú aplikáciu nasledovne:
java TEDemo 198
Mali by ste dodržiavať nasledujúci výstup:
198 halierov obsahuje 39 niklov 198 halierov obsahuje 19 centov 198 halierov obsahuje 7 štvrtín 198 halierov obsahuje 1 dolár
Skúmanie Enum
trieda
Zvažuje to kompilátor Java enum
byť syntaktický cukor. Po stretnutí s deklaráciou typesafe enum vygeneruje triedu, ktorej meno je určené deklaráciou. Táto trieda podtrieda abstrakt Enum
trieda, ktorá slúži ako základná trieda pre všetky typicky bezpečné enumy.
Enum
Formálny zoznam parametrov typu vyzerá príšerne, ale nie je to také ťažké pochopiť. Napríklad v kontexte Mince rozširuje Enum
, tento zoznam parametrov formálneho typu by ste interpretovali takto:
- Akákoľvek podtrieda
Enum
musí dodať argument skutočného typu preEnum
. Napríklad,Mince
Určuje hlavičkaEnum
. - Argument skutočného typu musí byť podtriedou
Enum
. Napríklad,Mince
je podtriedaEnum
. - Podtrieda
Enum
(ako naprMince
) musí postupovať podľa frázy, ktorá dodáva svoje vlastné meno (Mince
) ako argument aktuálneho typu.
Preskúmajte Enum
Dokumentácie Java a zistíte, že má prednosť java.lang.Objekt
je klon ()
, rovná sa ()
, dokončiť ()
, hashCode ()
a natiahnuť()
metódy. Okrem natiahnuť()
, všetky tieto prvoradé metódy sú deklarované konečné
aby ich nebolo možné prepísať v podtriede:
klon ()
je prepísané, aby sa zabránilo klonovaniu konštánt, aby nikdy neexistovala viac ako jedna kópia konštanty; inak by sa konštanty nedali porovnať pomocou==
a!=
.rovná sa ()
je prepísané na porovnanie konštánt prostredníctvom ich referencií. Konštanty s rovnakými identitami (==
) musí mať rovnaký obsah (rovná sa ()
) a rôzne identity znamenajú odlišný obsah.dokončiť ()
je prepísané, aby sa zabezpečilo, že konštanty nebude možné finalizovať.hashCode ()
je prepísané, pretožerovná sa ()
je prepísaný.natiahnuť()
je prepísané, aby sa vrátilo meno konštanty.
Enum
tiež poskytuje svoje vlastné metódy. Medzi tieto metódy patrí konečné
porovnať s()
(Enum
realizuje java.lang. Porovnateľné
rozhranie), getDeclaringClass ()
, názov()
a radové ()
metódy: