Programovanie

Ako používať bezpečné typy enums v jazyku Java

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.

stiahnuť Získajte kód Stiahnite si zdrojový kód pre príklady v tomto výučbe Java 101. Vytvoril Jeff Friesen pre JavaWorld /.

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, Stringkonš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 OblekKonš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ÝCHODa JUHkonš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. SEVERs 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 MinceKonš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.

EnumFormá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 pre Enum. Napríklad, MinceUrčuje hlavička Enum.
  • Argument skutočného typu musí byť podtriedou Enum. Napríklad, Mince je podtrieda Enum.
  • Podtrieda Enum (ako napr Mince) musí postupovať podľa frázy, ktorá dodáva svoje vlastné meno (Mince) ako argument aktuálneho typu.

Preskúmajte EnumDokumentácie Java a zistíte, že má prednosť java.lang.Objektje 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že rovná 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:

$config[zx-auto] not found$config[zx-overlay] not found