Programování

Jak používat bezpečné typy výčtu v Javě

Kód Java, který používá tradiční výčty typů, je problematický. Java 5 nám dala lepší alternativu v podobě výčtu typů. V tomto článku vás seznámím s výčty typů a typů výčtů, ukážu vám, jak deklarovat výčet typů a výčet a použít je v příkazu switch, a pojednám o přizpůsobení výčtu typů přidáním dat a chování. Článek končím prozkoumáním java.lang.Enum třída.

stáhnout Získat kód Stáhněte si zdrojový kód pro příklady v tomto výukovém programu Java 101. Vytvořil Jeff Friesen pro JavaWorld /.

Od výčtu typů až po bezpečné výčty

An vyjmenovaný typ určuje sadu souvisejících konstant jako své hodnoty. Mezi příklady patří týden dnů, standardní směry kompasu sever / jih / východ / západ, nominální hodnoty mincí měny a typy tokenů lexikálního analyzátoru.

Výčtové typy byly tradičně implementovány jako sekvence celočíselných konstant, což je demonstrováno následující sadou směrových konstant:

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 přístupem je několik problémů:

  • Nedostatek bezpečnosti typu: Vzhledem k tomu, že konstanta výčtu typu je pouze celé číslo, lze zadat celé číslo, kde je požadována konstanta. Dále lze na těchto konstantách provádět sčítání, odčítání a další matematické operace; například, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), což je nesmyslné.
  • Obor názvů není k dispozici: Konstanty výčtu typu musí mít předponu s nějakým (snad) jedinečným identifikátorem (např. DIR_), aby se zabránilo kolizím s konstantami jiného vyjmenovaného typu.
  • Křehkost: Protože výčtové konstanty typu jsou kompilovány do souborů tříd, kde jsou uloženy jejich doslovné hodnoty (ve fondech konstant), vyžaduje změna hodnoty konstanty opětovné sestavení těchto souborů třídy a souborů tříd aplikací, které na nich závisí. Jinak nedefinované chování nastane za běhu.
  • Nedostatek informací: Když se vytiskne konstanta, vygeneruje se její celočíselná hodnota. Tento výstup vám neřekne nic o tom, co představuje celočíselná hodnota. Neidentifikuje ani výčtový typ, ke kterému konstanta patří.

Použitím se můžete vyhnout problémům s „nedostatkem bezpečnosti typu“ a „nedostatkem informací“ řetězec java.lang konstanty. Můžete například určit statický konečný řetězec DIR_NORTH = "SEVER";. I když je konstantní hodnota smysluplnější, Tětiva- konstanty založené na principu stále trpí „nepřítomností jmenného prostoru“ a problémy s křehkostí. Na rozdíl od celočíselných srovnání také nemůžete porovnávat řetězcové hodnoty s == a != operátory (které porovnávají pouze reference).

Tyto problémy způsobily, že vývojáři vymysleli alternativu založenou na třídě známou jako Typický bezpečný výčet. Tento vzorec byl široce popsán a kritizován. Joshua Bloch představil vzor ve své položce 21 Efektivní průvodce programovacím jazykem Java (Addison-Wesley, 2001) a poznamenal, že má určité problémy; jmenovitě, že je nepohodlné agregovat konstanty výčtu bezpečných typů do sad a že konstanty výčtu nelze použít v přepínač prohlášení.

Zvažte následující příklad vzorově bezpečného výčtu. The Oblek třída ukazuje, jak můžete použít alternativu založenou na třídě k zavedení výčtu typu, který popisuje čtyři karty (kluby, diamanty, srdce a piky):

public final class Suit // Nemělo by být možné podtřídu Suit. {public static final Suit CLUBS = new Suit (); public static final Suit DIAMONDS = new Suit (); public static final Suit SRDCE = nový Suit (); public static final Suit SPADES = new Suit (); private Suit () {} // Nemělo by být možné zavést další konstanty. }

Chcete-li použít tuto třídu, měli byste zavést a Oblek proměnnou a přiřadit ji jednomu z OblekKonstanty:

Suit suit = Suit.DIAMONDS;

Možná budete chtít vyslýchat oblek v přepínač prohlášení jako je tento:

switch (oblek) {case Suit.CLUBS: System.out.println ("kluby"); přestávka; případ Suit.DIAMONDS: System.out.println ("diamanty"); přestávka; případ Suit.HEARTS: System.out.println ("srdce"); přestávka; case Suit.SPADES: System.out.println ("piky"); }

Když však narazí na kompilátor Java Oblek. KLUBY, ohlásí chybu s uvedením, že je vyžadován konstantní výraz. Můžete se pokusit problém vyřešit následovně:

switch (oblek) {case CLUBS: System.out.println ("kluby"); přestávka; případ DIAMONDS: System.out.println ("diamanty"); přestávka; případ SRDCE: System.out.println ("srdce"); přestávka; případ SPADES: System.out.println ("piky"); }

Když však narazí na kompilátor KLUBY, ohlásí chybu s uvedením, že symbol nebyl schopen najít. A to i v případě, že jste umístili Oblek v balíčku, importoval balíček a staticky importoval tyto konstanty, kompilátor by si stěžoval, že nemůže převést Oblek na int při setkání oblek v přepínač (oblek). Ohledně každého případ, kompilátor by také hlásil, že je vyžadován konstantní výraz.

Java nepodporuje vzor Typesafe Enum přepínač prohlášení. Představila však typicky bezpečný výčet funkce jazyka, která zapouzdřuje výhody vzoru při řešení jeho problémů, a tato funkce podporuje přepínač.

Deklarování výčtu typů a jeho použití v příkazu switch

Jednoduchá deklarace bezpečného výčtu v kódu Java vypadá jako jeho protějšky v jazycích C, C ++ a C #:

výčet Směr {SEVER, ZÁPAD, VÝCHOD, JIŽNÍ}

Tato deklarace používá klíčové slovo výčet zavést Směr jako výčet typů (speciální druh třídy), do kterého lze přidat libovolné metody a implementovat libovolná rozhraní. The SEVERNÍ, ZÁPAD, VÝCHODNÍ, a JIŽNÍkonstanty výčtu jsou implementovány jako konstantní specifická těla tříd, která definují anonymní třídy rozšiřující ohraničení Směr třída.

Směr a další typicky bezpečné výčty se rozšiřují Výčet a dědí různé metody, včetně hodnoty (), toString (), a porovnat s()z této třídy. Prozkoumáme to Výčet dále v tomto článku.

Výpis 1 deklaruje výše uvedený výčet a používá jej v a přepínač prohlášení. Ukazuje také, jak porovnat dvě konstanty výčtu a určit, která konstanta bude před druhou konstantou.

Výpis 1: TEDemo.java (verze 1)

public class TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static void main (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .value () [i]; System.out.println (d); switch (d) {case SEVER: System.out.println ("Přesunout na sever"); přestávka; případ ZÁPAD: System.out.println ("Přesunout na západ"); přestávka; případ EAST: System.out.println ("Přesunout na východ"); přestávka; případ JIŽNÍ: System.out.println ("Přesunout na jih"); přestávka; výchozí: assert false: "neznámý směr"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Výpis 1 deklaruje Směr typesafe enum a iteruje nad svými stálými členy, které hodnoty () se vrací. Pro každou hodnotu je přepínač prohlášení (vylepšeno o podporu typů bezpečných výčtů) vybere případ který odpovídá hodnotěd a vydá příslušnou zprávu. (Nepředponujete konstantu výčtu, např. SEVERNÍ, se svým typem výčtu.) Nakonec se vyhodnotí výpis 1 Direction.NORTH.compareTo (Direction.SOUTH) zjistit, zda SEVERNÍ přijde dříve JIŽNÍ.

Zkompilujte zdrojový kód následujícím způsobem:

javac TEDemo.java

Spustit zkompilovanou aplikaci následujícím způsobem:

java TEDemo

Měli byste dodržovat následující výstup:

SEVER Přesun na sever ZÁPAD Přesun na západ VÝCHOD Přesun na východ JIH Přesun na jih -3

Výstup ukazuje, že zděděné toString () metoda vrací název konstanty výčtu a to SEVERNÍ přijde dříve JIŽNÍ ve srovnání těchto výčtových konstant.

Přidání dat a chování do výčtu typů

Do výčtu typů můžete přidat data (ve formě polí) a chování (ve formě metod). Předpokládejme například, že musíte zavést enum pro kanadské mince a že tato třída musí poskytnout prostředky k vrácení počtu niklů, desetníků, čtvrtí nebo dolarů obsažených v libovolném počtu haléřů. Výpis 2 ukazuje, jak tento úkol splnit.

Výpis 2: TEDemo.java (verze 2)

enum Coin {NICKEL (5), // konstanty se musí objevit nejprve DIME (10), QUARTER (25), DOLLAR (100); // středník je vyžadován private final int valueInPennies; Coin (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int haléře) {návrat haléře / valueInPennies; }} public class TEDemo {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("usage: java TEDemo amountInPennies"); vrátit se; } 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"); }}

Výpis 2 nejprve deklaruje a Mince výčet Seznam parametrizovaných konstant identifikuje čtyři druhy mincí. Argument předaný každé konstantě představuje počet haléřů, které mince představuje.

Argument předaný každé konstantě je ve skutečnosti předán do Coin (int valueInPennies) konstruktor, který uloží argument do valuesInPennies pole instance. Tato proměnná je přístupná zevnitř toCoins () metoda instance. Rozděluje se na počet haléřů předaných toCoin ()Je haléře Parametr a tato metoda vrací výsledek, kterým je počet mincí v peněžní denominaci popsaný parametrem Mince konstantní.

V tomto okamžiku jste zjistili, že můžete deklarovat pole instance, konstruktory a metody instance v typově bezpečném výčtu. Nakonec je typický výčet v podstatě speciální druh třídy Java.

The TEDemo třídy hlavní() metoda nejprve ověří, že byl zadán jeden argument příkazového řádku. Tento argument se převede na celé číslo voláním java.lang.Integer třídy parseInt () metoda, která analyzuje hodnotu svého řetězcového argumentu na celé číslo (nebo vyvolá výjimku, když je zjištěn neplatný vstup). Budu k tomu mít ještě co říct Celé číslo a jeho třídy bratranců v budoucnosti Java 101 článek.

Kupředu hlavní() iteruje znovu MinceKonstanty. Protože tyto konstanty jsou uloženy v a Mince[] pole, hlavní() hodnotí Coin.values ​​(). Délka k určení délky tohoto pole. Pro každou iteraci indexu smyčky i, hlavní() hodnotí Coin.values ​​() [i] pro přístup k Mince konstantní. Vyvolá každou z toCoins () a toString () na této konstantě, což to dále dokazuje Mince je zvláštní druh třídy.

Zkompilujte zdrojový kód následujícím způsobem:

javac TEDemo.java

Spustit zkompilovanou aplikaci následujícím způsobem:

java TEDemo 198

Měli byste dodržovat následující výstup:

198 haléřů obsahuje 39 niklů 198 haléřů obsahuje 19 desetníků 198 haléřů obsahuje 7 čtvrtin 198 haléřů obsahuje 1 dolar

Za poznáním Výčet třída

Kompilátor Java uvažuje výčet být syntaktický cukr. Při narušení deklarace výčtu typesafe generuje třídu, jejíž název je určen deklarací. Tato třída podtřídy abstrakt Výčet třída, která slouží jako základní třída pro všechny typicky bezpečné výčty.

VýčetFormální seznam parametrů typu vypadá příšerně, ale není to tak těžké pochopit. Například v kontextu Mince rozšiřuje Enum, byste tento seznam parametrů formálního typu interpretovali takto:

  • Libovolná podtřída Výčet musí dodat argument skutečného typu Výčet. Například, MinceUrčuje záhlaví Výčet.
  • Argument skutečného typu musí být podtřídou Výčet. Například, Mince je podtřída Výčet.
  • Podtřída Výčet (jako Mince) musí následovat idiom, že dodává své vlastní jméno (Mince) jako argument skutečného typu.

Prozkoumat VýčetDokumentace Java a zjistíte, že má přednost java.lang.Objectje klon (), se rovná(), dokončit(), hashCode (), a toString () metody. Až na toString (), jsou deklarovány všechny tyto převažující metody finále takže je nelze přepsat v podtřídě:

  • klon () je přepsáno, aby se zabránilo klonování konstant, takže nikdy nebude existovat více než jedna kopie konstanty; jinak nelze konstanty porovnat pomocí == a !=.
  • se rovná() je přepsán k porovnání konstant pomocí jejich odkazů. Konstanty se stejnou identitou (==) musí mít stejný obsah (se rovná()) a různé identity znamenají odlišný obsah.
  • dokončit() je přepsáno, aby bylo zajištěno, že konstanty nelze finalizovat.
  • hashCode () je přepsáno, protože se rovná() je přepsán.
  • toString () je přepsáno, aby vrátilo jméno konstanty.

Výčet také poskytuje své vlastní metody. Mezi tyto metody patří fináleporovnat s() (Výčet realizuje java.lang. Srovnatelné rozhraní), getDeclaringClass (), název(), a pořadové () metody: