Programování

Pomocí == (nebo! =) Porovnejte výčty Java

Většina nových vývojářů jazyka Java rychle zjistí, že by měli obecně porovnávat řetězce Java Strings pomocí String.equals (Object) spíše než pomocí ==. To je novým vývojářům opakovaně zdůrazňováno a posilováno, protože oni Skoro pořád znamená porovnat obsah řetězce (skutečné znaky tvořící řetězec) spíše než identitu řetězce (jeho adresu v paměti). Tvrdím, že bychom měli tuto představu posílit == lze použít místo Enum.equals (Object). Ve zbývající části tohoto příspěvku uvádím své odůvodnění tohoto tvrzení.

Věřím, že používám čtyři důvody == porovnat výčty Java je Skoro pořád vhodnější než použít metodu „rovná se“:

  1. The == na enums poskytuje stejné očekávané srovnání (obsah) jako se rovná
  2. The == na enums je pravděpodobně čitelnější (méně podrobný) než se rovná
  3. The == na enums je bezpečnější než null se rovná
  4. The == on enums poskytuje kontrolu za kompilace (statickou) spíše než za běhu

Druhý důvod uvedený výše („pravděpodobně čitelnější“) je zjevně věcí názoru, ale s částí „méně verbální“ lze souhlasit. První důvod, proč obecně dávám přednost == při porovnání výčtu je důsledkem toho, jak specifikace jazyka jazyka popisuje výčty. Oddíl 8.9 („Enums“) uvádí:

Jedná se o chybu kompilace při pokusu o výslovné vytvoření instance typu výčtu. Konečná metoda klonování v Enum zajišťuje, že konstanty výčtu nelze nikdy klonovat, a speciální léčba mechanismem serializace zajišťuje, že duplicitní instance se nikdy nevytvoří v důsledku deserializace. Reflexní vytváření instancí typů enum je zakázáno. Společně tyto čtyři věci zajišťují, že žádné instance typu výčtu neexistují nad rámec definovaných konstantami výčtu.

Protože existuje pouze jedna instance každé konstanty výčtu, je přípustné použít operátor == místo metody equals při porovnávání dvou odkazů na objekty, pokud je známo, že alespoň jeden z nich odkazuje na konstantu výčtu. (Metoda equals v Enumu je konečná metoda, která na svůj argument vyvolá pouze super.equals a vrátí výsledek, čímž provede srovnání identity.)

Výňatek ze specifikace uvedené výše naznačuje a poté výslovně uvádí, že je bezpečné používat == operátor porovnat dva výčty, protože neexistuje způsob, jak může existovat více než jedna instance stejné konstanty výčtu.

Čtvrtá výhoda == přes .se rovná při porovnání výčtu má co do činění s bezpečností při kompilaci. Použití == vynutí přísnější časovou kontrolu kompilace než pro .se rovná protože Object.equals (Object) musí podle smlouvy nabývat svévolnosti Objekt. Když používám staticky zadaný jazyk, jako je Java, věřím, že co nejvíce využiji výhod tohoto statického psaní. Jinak bych použil dynamicky psaný jazyk. Věřím, že jedním z opakujících se témat Efektivní Javy je právě toto: dávejte přednost statické kontrole typu, kdykoli je to možné.

Předpokládejme například, že jsem zavolal vlastní enum Ovoce a zkusil jsem to porovnat s třídou java.awt.Color. Za použití == operátor mi umožňuje získat chybu kompilace (včetně předběžného upozornění v mém oblíbeném Java IDE) problému. Zde je výpis kódu, který se pokouší porovnat vlastní výčet s třídou JDK pomocí == operátor:

/ ** * Uveďte, pokud je k dispozici Barva je meloun. * * Implementace této metody je komentována, aby se předešlo chybě kompilátoru *, která legitimně zakazuje == porovnávat dva objekty, které nejsou a * nemohou být nikdy to samé. * * @param candidateColor Color, která nikdy nebude meloun. * @return Nikdy by to neměla být pravda. * / public boolean isColorWatermelon (java.awt.Color candidateColor) {// Toto srovnání Fruit to Color povede k chybě kompilátoru: // error: neporovnatelné typy: Fruit a Color return Fruit.WATERMELON == candidateColor; } 

Chyba kompilátoru se zobrazí na následujícím snímku obrazovky.

I když nejsem fanouškem chyb, dávám přednost tomu, aby byly zachyceny staticky v době kompilace, spíše než v závislosti na době běhu. Kdybych použil se rovná metoda pro toto srovnání, kód by byl zkompilován dobře, ale metoda by se vždy vrátila Nepravdivé nepravda, protože neexistuje žádný způsob a poprášení. příklady. ovoce enum se bude rovnat a java.awt.Color třída. Nedoporučuji to, ale tady je srovnávací metoda, která se používá .se rovná:

/ ** * Uveďte, zda je poskytnutá barva malina. Toto je naprostý nesmysl *, protože Color se nikdy nemůže rovnat Fruit, ale kompilátor umožňuje tuto * kontrolu a pouze určení běhu může naznačovat, že nejsou * stejné, i když se nikdy nemohou rovnat. Takto NENÍ dělat věci. * * @param candidateColor Color, která nikdy nebude malina. * @return {@code false}. Vždy. * / public boolean isColorRaspberry (java.awt.Color candidateColor) {// // NEDĚLEJTE TO: Ztráta úsilí a zavádějící kód !!!!!!!! // vrátit Fruit.RASPBERRY.equals (candidateColor); } 

„Pěknou“ věcí na výše uvedeném je nedostatek chyb při kompilaci. Krásně se to kompiluje. Bohužel se za to platí potenciálně vysokou cenou.

Poslední výhoda, kterou jsem uvedl v používání == spíše než Enum. Equals při porovnání výčtu je vyhýbání se obávané NullPointerException. Jak jsem uvedl v Effective Java NullPointerException Handling, obecně se chci vyhnout neočekávaným NullPointerExceptions. Existuje omezený soubor situací, ve kterých opravdu chci, aby se existence nuly považovala za výjimečný případ, ale často dávám přednost elegantnějšímu hlášení problému. Výhodou srovnání enums s == je, že null lze srovnávat s nenulovým výčtem, aniž by došlo k a NullPointerException (NPE). Výsledek tohoto srovnání samozřejmě je Nepravdivé.

Jeden způsob, jak se vyhnout NPE při používání .equals (Object) je vyvolat se rovná metoda proti konstantní výčtu nebo známému nenulovému výčtu a poté předat potenciální výčet sporného znaku (případně null) jako parametr do se rovná metoda. To se často v Javě provádí roky pomocí Strings, aby se zabránilo NPE. Nicméně, s == operátor, na pořadí porovnání nezáleží. To se mi líbí.

Udělal jsem své argumenty a nyní přejdu k několika příkladům kódu. Dalším seznamem je realizace hypotetického výčtu ovoce zmíněného výše.

Fruit.java

příklady zásilky; veřejné enum Ovoce {JABLKO, BANÁNY, ČERSTVINY, ČEREŠNICE, TŘEŠEŇ, HROZNA, KIWI, MANGO, ORANŽE, MALINY, JAHODY, RAJČATA, VODNÍ MELON} 

Dalším výpisem kódu je jednoduchá třída Java, která poskytuje metody pro zjišťování, zda je určitý výčet nebo objekt určitým ovocem. Normálně bych dal takové kontroly do samotného výčtu, ale fungují lépe v samostatné třídě zde pro mé ilustrativní a demonstrativní účely. Tato třída zahrnuje dvě metody uvedené výše pro porovnání Ovoce na Barva s oběma == a se rovná. Samozřejmě, že metoda používá == k porovnání výčtu s třídou bylo nutné tuto část komentovat, aby se správně sestavila.

EnumComparisonMain.java

příklady zásilky; veřejná třída EnumComparisonMain {/ ** * Uveďte, zda poskytované ovoce je meloun ({@code true} nebo ne * ({@code false}). * * @param candidateFruit Ovoce, které může nebo nemusí být meloun; null je * naprosto přijatelné (přineste to!). * @return {@code true} pokud je ovoce ovoce meloun; {@code false} pokud * pokud ovoce NENÍ meloun. * / public boolean isFruitWatermelon (Fruit candidateFruit) {návrat kandidátFruit = = Fruit.WATERMELON;} / ** * Uveďte, zda je poskytnutý objekt Fruit.WATERMELON ({@code true}) nebo * ne ({@code false}). * * @Param candidateObject Objekt, který může nebo nemusí být meloun a nemusí * být ani Fruit! * @return {@code true}, pokud je poskytnutým objektem Fruit.WATERMELON; * {@code false}, pokud poskytnutým objektem není Fruit.WATERMELON. * / public boolean isObjectWatermelon (Object candidateObject ) {return candidateObject == Fruit.WATERMELON;} / ** * Uveďte, zda je k dispozici Barva je meloun. * * Implementace této metody je komentována vyhněte se chybě kompilátoru *, která legitimně zakazuje == porovnávat dva objekty, které nejsou a * nemůže být nikdy to samé. * * @param candidateColor Color, která nikdy nebude meloun. * @return Nikdy by to neměla být pravda. * / public boolean isColorWatermelon (java.awt.Color candidateColor) {// Musel jsem komentovat srovnání Fruit to Color, aby se zabránilo chybě kompilátoru: // error: neporovnatelné typy: Fruit and Color return /*Fruit.WATERMELON == candidateColor * / Nepravdivé; } / ** * Uveďte, zda poskytované ovoce je jahoda ({@code true}), nebo ne * ({@code false}). * * @param candidateFruit Ovoce, které může, ale nemusí být jahoda; null je * naprosto přijatelné (přineste to!). * @return {@code true} pokud je ovoce jahoda; {@code false} pokud * za předpokladu, že ovoce NENÍ jahodové. * / public boolean isFruitStrawberry (Fruit candidateFruit) {návrat Fruit.STRAWBERRY == candidateFruit; } / ** * Uveďte, zda poskytované ovoce je malina ({@code true}) nebo ne * ({@code false}). * * @param candidateFruit Ovoce, které může, ale nemusí být malina; null je * zcela a zcela nepřijatelné; prosím neprosím null, prosím, * prosím, prosím. * @return {@code true}, pokud je ovoce malina; {@code false} pokud * za předpokladu, že ovoce NENÍ malina. * / public boolean isFruitRaspberry (Fruit candidateFruit) {návrat candidateFruit.equals (Fruit.RASPBERRY); } / ** * Uveďte, zda je poskytnutý objekt Fruit.RASPBERRY ({@code true}) nebo * ne ({@code false}). * * @param candidateObject Objekt, který může nebo nemusí být malina a může * nebo nemusí být ani ovoce! * @return {@code true}, pokud je poskytnut Objekt je Fruit.RASPBERRY; {@code false} * pokud to není ovoce nebo malina. * / public boolean isObjectRaspberry (Object candidateObject) {return candidateObject.equals (Fruit.RASPBERRY); } / ** * Uveďte, zda je poskytnutá barva malina. Toto je naprostý nesmysl *, protože Color se nikdy nemůže rovnat Fruit, ale kompilátor umožňuje tuto * kontrolu a pouze určení běhu může naznačovat, že nejsou * stejné, i když se nikdy nemohou rovnat. Takto NENÍ dělat věci. * * @param candidateColor Color, která nikdy nebude malina. * @return {@code false}. Vždy. * / public boolean isColorRaspberry (java.awt.Color candidateColor) {// // NEDĚLEJTE TO: Ztráta úsilí a zavádějící kód !!!!!!!! // vrátit Fruit.RASPBERRY.equals (candidateColor); } / ** * Uveďte, zda poskytované ovoce je hroznové víno ({@code true}), nebo ne * ({@code false}). * * @param candidateFruit Ovoce, které může, ale nemusí být hroznem; null je * naprosto přijatelné (přineste to!). * @return {@code true}, pokud je ovoce hroznem; {@code false} pokud * za předpokladu, že ovoce NENÍ hroznem. * / public boolean isFruitGrape (Fruit candidateFruit) {návrat Fruit.GRAPE.equals (candidateFruit); }} 

Rozhodl jsem se přiblížit demonstraci myšlenek zachycených výše uvedenými metodami pomocí jednotkových testů. Zejména využívám GroovyTestCase od Groovy. Tato třída pro použití testování jednotek poháněných Groovy je v dalším seznamu kódů.

EnumComparisonTest.groovy

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