Programování

Vytvořte výčet konstant v Javě

Sada „vyčíslitelných konstant“ je uspořádaná kolekce konstant, které lze spočítat, například čísla. Tato vlastnost vám umožňuje používat je jako čísla k indexování pole, nebo je můžete použít jako proměnnou indexu ve smyčce for. V Javě jsou tyto objekty nejčastěji označovány jako „enumerované konstanty“.

Použití výčtu konstant může zajistit lepší čitelnost kódu. Můžete například chtít definovat nový datový typ s názvem Barva s konstantami ČERVENÉ, ZELENÉ a MODRÉ jako jeho možné hodnoty. Myšlenkou je mít Color jako atribut jiných objektů, které vytvoříte, například Car objekty:

 třída Auto {Barva barva; ...} 

Pak můžete napsat jasný a čitelný kód, například takto:

 myCar.color = ČERVENÁ; 

místo něčeho jako:

 myCar.color = 3; 

Ještě důležitějším atributem vyjmenovaných konstant v jazycích, jako je Pascal, je to, že jsou bezpečné pro typ. Jinými slovy, atributu color nelze přiřadit neplatnou barvu - vždy musí být ČERVENÁ, ZELENÁ nebo MODRÁ. Naproti tomu, pokud barevná proměnná byla int, můžete jí přiřadit libovolné platné celé číslo, i když toto číslo nepředstavuje platnou barvu.

Tento článek poskytuje šablonu pro vytváření výčtových konstant, které jsou:

  • Typ bezpečný
  • Tisknutelné
  • Objednáno, pro použití jako index
  • Propojené, pro smyčkování dopředu nebo dozadu
  • Vyčíslitelné

V budoucím článku se naučíte, jak rozšířit výčtové konstanty k implementaci chování závislého na stavu.

Proč nepoužívat statické finále?

Běžný mechanismus pro výčtové konstanty používá statické proměnné final int, například takto:

 statický konečný int ČERVENÉ = 0; statický konečný int ZELENÁ = 1; statický konečný int MODRÝ = 2; ... 

Statické finále je užitečné

Protože jsou konečné, hodnoty jsou konstantní a neměnné. Protože jsou statické, jsou vytvořeny pouze jednou pro třídu nebo rozhraní, ve kterém jsou definovány, namísto jednou pro každý objekt. A protože jsou celočíselnými proměnnými, lze je vyjmenovat a použít jako index.

Můžete například napsat smyčku a vytvořit seznam oblíbených barev zákazníka:

 for (int i = 0; ...) {if (customerLikesColor (i)) {oblíbenéColors.add (i); }} 

Můžete také indexovat do pole nebo do vektoru pomocí proměnných, abyste získali hodnotu spojenou s barvou. Předpokládejme například, že máte deskovou hru, která má pro každého hráče různě zbarvené figurky. Řekněme, že pro každý kousek barvy máte bitmapu a volanou metodu Zobrazit() který zkopíruje tuto bitmapu do aktuálního umístění. Jedním ze způsobů, jak dát kousek na hrací plochu, může být něco jako toto:

PiecePicture redPiece = nový PiecePicture (RED); PiecePicture greenPiece = nový PiecePicture (ZELENÝ); PiecePicture bluePiece = nový PiecePicture (MODRÝ);

void placePiece (int umístění, int barva) {setPosition (umístění); if (color == RED) {display (redPiece); } else if (color == ZELENÁ) {display (greenPiece); } else {display (bluePiece); }}

Ale pomocí celočíselných hodnot k indexování do pole kusů můžete kód zjednodušit na:

 PiecePicture [] piece = {nový PiecePicture (ČERVENÝ), nový PiecePicture (ZELENÝ), nový PiecePicture (MODRÝ)}; void placePiece (int umístění, int barva) {setPosition (umístění); displej (kus [barva]); } 

Hlavní schopnost statických konečných celých čísel je schopnost smyčky přes celou řadu konstant a indexování do pole nebo vektoru. A když počet možností poroste, efekt zjednodušení je ještě větší.

Ale statické finále je riskantní

Přesto existuje několik nevýhod používání statických konečných celých čísel. Hlavní nevýhodou je nedostatek bezpečnosti typu. Jakékoli celé číslo, které je vypočítáno nebo načteno, lze použít jako „barvu“ bez ohledu na to, zda má smysl tak učinit. Můžete smyčku za konec definovaných konstant nebo přestat pokrývat všechny, což se může snadno stát, když přidáte nebo odeberete konstantu ze seznamu, ale zapomenete upravit index smyčky.

Například vaše smyčka preferencí barev může číst takto:

 for (int i = 0; i <= MODRÉ; i ++) {if (customerLikesColor (i)) {oblíbenéColors.add (i); }} 

Později můžete přidat novou barvu:

 statický konečný int ČERVENÉ = 0; statický konečný int ZELENÁ = 1; statický konečný int MODRÝ = 2; statický konečný int MAGENTA = 3; 

Nebo můžete jeden odebrat:

 statický konečný int ČERVENÉ = 0; statický konečný int MODRÁ = 1; 

V obou případech program nebude fungovat správně. Pokud odeberete barvu, zobrazí se runtime chyba, která upozorňuje na problém. Pokud přidáte barvu, nezískáte vůbec žádnou chybu - program jednoduše nedokáže pokrýt všechny možnosti barev.

Další nevýhodou je nedostatek čitelného identifikátoru. Pokud k zobrazení aktuální volby barev použijete okno se zprávou nebo výstup z konzoly, získáte číslo. Díky tomu je ladění docela obtížné.

Problémy s vytvořením čitelného identifikátoru se někdy řeší pomocí statických konstant koncové řetězce, například takto:

 statický konečný řetězec ČERVENÁ = "červená" .intern (); ... 

Za použití internovat() metoda zaručuje, že ve vnitřním fondu řetězců je pouze jeden řetězec s tímto obsahem. Ale pro internovat() aby byl efektivní, musí jej používat každý řetězec nebo proměnná řetězce, která je kdy srovnávána s RED. Dokonce i poté statické konečné řetězce neumožňují opakování nebo indexování do pole a stále neřeší problém bezpečnosti typu.

Typ bezpečnosti

Problém se statickými konečnými celými čísly spočívá v tom, že proměnné, které je používají, jsou ve své podstatě neomezené. Jsou to proměnné typu int, což znamená, že mohou obsahovat jakékoli celé číslo, nejen konstanty, které měly obsahovat. Cílem je definovat proměnnou typu Color, aby se při každém přiřazení neplatné hodnoty k této proměnné zobrazila chyba kompilace spíše než runtime chyba.

Elegantní řešení bylo poskytnuto v článku Philipa Bishopa v JavaWorld, „Typesafe constants in C ++ and Java.“

Myšlenka je opravdu jednoduchá (jakmile ji uvidíte!):

veřejná finální třída Barva {// finální třída !! private Color () {} // soukromý konstruktor !!

veřejná statická konečná barva ČERVENÁ = nová barva (); public static final Barva ZELENÁ = nová Barva (); public static final Barva MODRÁ = nová Barva (); }

Protože třída je definována jako konečná, nelze ji podtřídy. Z toho nebudou vytvořeny žádné další třídy. Protože konstruktor je soukromý, jiné metody nemohou použít třídu k vytvoření nových objektů. Jediné objekty, které budou kdy vytvořeny s touto třídou, jsou statické objekty, které si třída vytvoří sama při prvním odkazu na třídu! Tato implementace je variantou vzoru Singleton, který omezuje třídu na předdefinovaný počet instancí. Tento vzor můžete použít k vytvoření přesně jedné třídy, kdykoli potřebujete Singleton, nebo jej použít, jak je znázorněno zde, k vytvoření pevného počtu instancí. (Singletonův vzor je definován v knize Návrhové vzory: Prvky opakovaně použitelného objektově orientovaného softwaru Gamma, Helm, Johnson a Vlissides, Addison-Wesley, 1995. Odkaz na tuto knihu najdete v části Zdroje.)

Část této definice třídy, která ohromuje, je, že třída používá sám vytvářet nové objekty. Když poprvé odkazujete na ČERVENÉ, neexistuje. Ale akt přístupu ke třídě, ve které je RED definována, způsobí její vytvoření spolu s dalšími konstantami. Je pravda, že tento druh rekurzivního odkazu je poměrně obtížné vizualizovat. Výhodou je ale úplná bezpečnost typu. Proměnné typu Color nelze nikdy přiřadit nic jiného než ČERVENÉ, ZELENÉ nebo MODRÉ objekty, které Barva třída vytváří.

Identifikátory

Prvním vylepšením třídy konstantních výčtových typů je vytvoření řetězcové reprezentace konstant. Chcete být schopni vytvořit čitelnou verzi hodnoty s takovým řádkem:

 System.out.println (myColor); 

Kdykoli odešlete objekt do výstupního proudu znaků, jako je System.out, a kdykoli zřetězíte objekt na řetězec, Java automaticky vyvolá toString () metoda pro tento objekt. To je dobrý důvod k definování a toString () metoda pro každou novou třídu, kterou vytvoříte.

Pokud třída nemá a toString () Metoda se hierarchie dědičnosti kontroluje, dokud není nalezena. Na vrcholu hierarchie je toString () metoda v Objekt class vrací název třídy. Takže toString () metoda vždy má nějaký význam, ale většinou nebude výchozí metoda příliš užitečná.

Zde je úprava Barva třída, která poskytuje užitečné toString () metoda:

veřejná závěrečná třída Barva { soukromé ID řetězce; soukromá barva (Řetězec anID) {this.id = anID; } public String toString () {vrátit this.id; }

veřejná statická konečná barva ČERVENÁ = nová barva (

"Červené"

); veřejná statická finální Barva ZELENÁ = nová Barva (

"Zelená"

); veřejná statická finální Barva MODRÁ = nová Barva (

"Modrý"

); }

Tato verze přidává soukromou proměnnou řetězce (id). Konstruktor byl upraven tak, aby převzal argument String a uložil jej jako ID objektu. The toString () metoda pak vrátí ID objektu.

Jeden trik, kterým můžete vyvolat toString () metoda využívá skutečnosti, že je automaticky vyvolána, když je objekt zřetězen na řetězec. To znamená, že můžete vložit název objektu do dialogového okna zřetězením na nulový řetězec pomocí řádku, jako je následující:

 textField1.setText ("" + mojeColor); 

Pokud nemáte rádi všechny závorky v Lispu, zjistíte, že je o něco čitelnější než alternativa:

 textField1.setText (myColor.toString ()); 

Je také snazší zajistit správný počet závěrečných závorek!

Objednávání a indexování

Další otázkou je, jak indexovat do vektoru nebo pole pomocí členů

Barva

třída. Mechanismus bude přiřadit pořadové číslo každé konstantě třídy a odkazovat na ni pomocí atributu

.ord

, takhle:

 void placePiece (int umístění, int barva) {setPosition (umístění); displej (kus [barva.ord]); } 

I když se připíná .ord převést odkaz na barva do čísla není nijak zvlášť hezké, není to ani nijak zvlášť rušivé. Vypadá to jako docela rozumný kompromis pro typicky bezpečné konstanty.

Takto jsou přiřazena pořadová čísla:

public final class Color {private String id; veřejný závěrečný záměr;private static int upperBound = 0; private Color (String anID) {this.id = anID; this.ord = upperBound ++; } public String toString () {vrátit this.id; } public static int size () {return upperBound; }

veřejná statická konečná barva ČERVENÁ = nová barva („červená“); veřejná statická konečná barva ZELENÁ = nová barva („zelená“); veřejná statická finální Barva MODRÁ = nová Barva („modrá“); }

Tento kód používá novou definici JDK verze 1.1 proměnné "blank final" - proměnné, které je přiřazena hodnota pouze jednou a pouze jednou. Tento mechanismus umožňuje každému objektu mít vlastní nestatickou konečnou proměnnou, ord, které budou při vytváření objektu přiřazeny jednou a které poté zůstanou neměnné. Statická proměnná horní hranice sleduje další nevyužitý index v kolekci. Tato hodnota se stává ord atribut při vytvoření objektu, po kterém se zvýší horní mez.

Pro kompatibilitu s Vektor třída, metoda velikost() je definováno pro vrácení počtu konstant, které byly definovány v této třídě (což je stejné jako horní mez).

Purista by mohl rozhodnout, že proměnná ord by měly být soukromé a pojmenovaná metoda ord () by měl vrátit - pokud ne, metoda s názvem getOrd (). Přikláním se k přímému přístupu k atributu, a to ze dvou důvodů. První je, že pojem ordinálu je jednoznačně konceptem int. Je malá pravděpodobnost, pokud vůbec, že ​​by se implementace někdy změnila. Druhým důvodem je to, co opravdu máte chci je schopnost používat objekt, jako by to byl int, jak byste mohli v jazyce, jako je Pascal. Například můžete chtít použít atribut barva indexovat pole. K tomu však nemůžete použít objekt Java. Opravdu byste chtěli říct:

 displej (kus [barva]); // žádoucí, ale nefunguje 

Ale to nemůžete udělat. Minimální změna nutná k získání toho, co chcete, je přístup k atributu, například takto:

 displej (kus [color.ord]); // nejblíže žádoucímu 

místo zdlouhavé alternativy:

 displej (kus [color.ord ()]); // další závorky 

nebo ještě delší:

 display (piece [color.getOrd ()]); // další závorky a text 

Jazyk Eiffel používá stejnou syntaxi pro přístup k atributům a vyvolání metod. To by byl ideál. Vzhledem k nutnosti výběru jednoho nebo druhého jsem však přistoupil k přístupu ord jako atribut. S trochou štěstí identifikátor ord v důsledku opakování se stane tak známým, že jeho používání bude vypadat stejně přirozeně jako psaní int. (Jakkoli to může být přirozené.)

Opakování

Dalším krokem je možnost iterace přes konstanty třídy. Chcete mít možnost smyčky od začátku do konce:

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

nebo od konce zpět na začátek:

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

Tyto úpravy používají statické proměnné ke sledování posledního vytvořeného objektu a jeho propojení s dalším objektem:

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