Programmering

Brug == (eller! =) Til at sammenligne Java Enums

De fleste nye Java-udviklere lærer hurtigt, at de generelt skal sammenligne Java Strings ved hjælp af String.equals (Object) snarere end at bruge ==. Dette understreges og forstærkes gentagne gange for nye udviklere, fordi de næsten altid betyder at sammenligne strengindhold (de faktiske tegn, der danner strengen) snarere end strengens identitet (dens adresse i hukommelsen). Jeg hævder, at vi skal styrke forestillingen om det == kan bruges i stedet for Enum.equals (Object). Jeg giver min begrundelse for denne påstand i resten af ​​dette indlæg.

Der er fire grunde, som jeg tror bruger == at sammenligne Java enums er næsten altid at foretrække frem for at bruge metoden "lig":

  1. Det == på enums giver den samme forventede sammenligning (indhold) som lige med
  2. Det == på enums er uden tvivl mere læselig (mindre detaljeret) end lige med
  3. Det == på enums er mere nul-sikkert end lige med
  4. Det == på enums giver kompileringstid (statisk) kontrol i stedet for runtime kontrol

Den anden årsag, der er anført ovenfor ("uden tvivl mere læsbar"), er naturligvis et spørgsmål om mening, men den del om "mindre detaljeret" kan man blive enige om. Den første grund til, at jeg generelt foretrækker det == når sammenligning af enums er en konsekvens af, hvordan Java Language Specification beskriver enums. Afsnit 8.9 ("Enums") siger:

Det er en kompileringstidsfejl at forsøge eksplicit at instantiere en enum-type. Den endelige klonmetode i Enum sikrer, at enumkonstanter aldrig kan klones, og den specielle behandling ved hjælp af serialiseringsmekanismen sikrer, at duplikatforekomster aldrig oprettes som et resultat af deserialisering. Reflekterende instantiering af enumtyper er forbudt. Tilsammen sikrer disse fire ting, at der ikke eksisterer forekomster af enumtype ud over dem, der er defineret af enumkonstanterne.

Da der kun er en forekomst af hver enumkonstant, er det tilladt at bruge operatoren == i stedet for ligemetoden, når man sammenligner to objekthenvisninger, hvis det er kendt, at mindst en af ​​dem henviser til en enumkonstant. (Ligemetoden i Enum er en endelig metode, der blot påberåber sig super. Ligestilling på sit argument og returnerer resultatet og udfører således en identitetssammenligning.)

Uddraget fra specifikationen vist ovenfor antyder og angiver derefter eksplicit, at det er sikkert at bruge == operatør til at sammenligne to enums, fordi der ikke er nogen måde, at der kan være mere end en forekomst af den samme enumkonstant.

Den fjerde fordel til == over .lige med når man sammenligner enums, har det at gøre med kompileringstidssikkerhed. Brugen af == tvinger en strengere kompileringstidskontrol end den for .lige med fordi Object.equals (Object) efter kontrakt skal tage vilkårlig Objekt. Når jeg bruger et statisk skrevet sprog som Java, tror jeg på at udnytte fordelene ved denne statiske typing så meget som muligt. Ellers bruger jeg et dynamisk skrevet sprog. Jeg tror, ​​at et af de tilbagevendende temaer i Effektiv Java er netop det: foretrækker statisk typekontrol, når det er muligt.

Antag for eksempel, at jeg havde et brugerdefineret enum kaldet Frugt og jeg forsøgte at sammenligne det med klassen java.awt.Color. Bruger == giver mig mulighed for at få en kompileringsfejl (inklusive forhåndsmeddelelse i min foretrukne Java IDE) om problemet. Her er en kodeliste, der forsøger at sammenligne et brugerdefineret enum med en JDK-klasse ved hjælp af == operatør:

/ ** * Angiv, hvis det er givet Farve er en vandmelon. * * Denne metodes implementering kommenteres for at undgå en kompilatorfejl *, der legitimt tillader == at sammenligne to objekter, der ikke er og * ikke kan være den samme ting nogensinde. * * @param kandidatFarvefarve, der aldrig bliver en vandmelon. * @return Bør aldrig være sandt. * / public boolean isColorWatermelon (java.awt.Color kandidatFarve) {// Denne sammenligning af frugt til farve vil føre til kompilatorfejl: // fejl: uforlignelige typer: Frugt og farve returnerer Frugt. WATERMELON == kandidatFarve; } 

Compilerfejlen vises i skærmbillede, der kommer næste.

Selvom jeg ikke er fan af fejl, foretrækker jeg, at de fanges statisk på kompileringstidspunktet i stedet for afhængigt af runtime-dækning. Havde jeg brugt lige med metode til denne sammenligning, ville koden have kompileret fint, men metoden ville altid vende tilbage falsk falsk, fordi der ikke er nogen måde a dustin. eksempler. frugt enum vil være lig med a java.awt.Farve klasse. Jeg kan ikke anbefale det, men her er sammenligningsmetoden ved hjælp af .lige med:

/ ** * Angiv, om den leverede farve er et hindbær. Dette er fuldstændig vrøvl *, fordi en farve aldrig kan svare til en frugt, men kompilatoren tillader denne * kontrol, og kun en runtime-bestemmelse kan indikere, at de ikke er * lige, selvom de aldrig kan være lige. Sådan skal IKKE gøre ting. * * @param kandidatFarve farve, der aldrig bliver et hindbær. * @retur {@code false}. Altid. * / public boolean isColorRaspberry (java.awt.Color kandidatColor) {// // IKKE GØR DETTE: Spild af indsats og vildledende kode !!!!!!!! // returner Fruit.RASPBERRY.equals (candidColor); } 

Den "pæne" ting ved ovenstående er manglen på kompileringsfejl. Det kompilerer smukt. Desværre betales dette for en potentielt høj pris.

Den endelige fordel, jeg nævnte ved at bruge == hellere end Enum. Ligestilling når man sammenligner enums, er det at undgå den frygtede NullPointerException. Som jeg sagde i Effektiv Java NullPointerException Handling, kan jeg generelt lide at undgå uventede NullPointerExceptions. Der er et begrænset sæt situationer, hvor jeg virkelig ønsker, at eksistensen af ​​et nul skal behandles som et ekstraordinært tilfælde, men ofte foretrækker jeg en mere yndefuld rapportering af et problem. En fordel ved at sammenligne enums med == er, at en null kan sammenlignes med en ikke-null enum uden at støde på en NullPointerException (NPE). Resultatet af denne sammenligning er naturligvis falsk.

En måde at undgå NPE ved brug .equals (Objekt) er at påberåbe sig lige med metode mod en enumkonstant eller en kendt ikke-nul enum og derefter overføre den potentielle enum af tvivlsom karakter (muligvis nul) som parameter til lige med metode. Dette er ofte blevet gjort i årevis i Java med strenge for at undgå NPE. Dog med == operatør, sammenligningsrækkefølge betyder ikke noget. Det kan jeg lide.

Jeg har fremsat mine argumenter, og nu går jeg videre til nogle kodeeksempler. Den næste liste er en realisering af den tidligere nævnte hypotetiske Fruit enum.

Frugt.java

pakke dustin. eksempler; public enum Fruit {APPLE, BANANA, BLACKBERRY, BLUEBERRY, CHERRY, DRAPE, KIWI, MANGO, ORANGE, RASPBERRY, STRAWBERRY, TOMATO, WATERMELON} 

Den næste kodeliste er en simpel Java-klasse, der giver metoder til at detektere, om et bestemt enum eller objekt er en bestemt frugt. Jeg ville normalt lægge checks som disse i selve enummet, men de fungerer bedre i en separat klasse her til mine illustrative og demonstrative formål. Denne klasse inkluderer de to metoder, der er vist tidligere til sammenligning Frugt til Farve med begge == og lige med. Selvfølgelig er metoden ved hjælp af == at sammenligne en enum med en klasse måtte have den del kommenteret for at kompilere ordentligt.

EnumComparisonMain.java

pakke dustin. eksempler; offentlig klasse EnumComparisonMain {/ ** * Angiv, om frugt, der leveres, er en vandmelon ({@code true} eller ej * ({@code false)). * * @param kandidat Frugt Frugt, der måske eller måske ikke er en vandmelon; null er * helt acceptabelt (kom med det!). * @return {@code true} hvis frugt er vandmelon; {@code false} hvis * forudsat frugt IKKE er vandmelon. * / public boolean isFruitWatermelon (Frugt kandidatFrugt) {retur kandidatFrugt = = Fruit.WATERMELON;} / ** * Angiv, om det angivne objekt er en Fruit.WATERMELON ({@code true}) eller * ikke ({@code false)). * * @Param candidObject Objekt, der måske eller måske ikke er et vandmelon og kan * ikke engang være en frugt! * @return {@code true} hvis det angivne objekt er en Fruit.WATERMELON; * {@code false} hvis det angivne objekt ikke er Fruit.WATERMELON. * / public boolean isObjectWatermelon (Objekt kandidatObject ) {return candidObject == Fruit.WATERMELON;} / ** * Angiv, hvis det er givet Farve er en vandmelon. * * Denne metodes implementering er kommenteret til undgå en kompilatorfejl *, der legitimt tillader == at sammenligne to objekter, der ikke er og * ikke kan være den samme ting nogensinde. * * @param kandidat Farve Farve, der aldrig bliver en vandmelon. * @return Bør aldrig være sandt. * / public boolean isColorWatermelon (java.awt.Color candidColor) {// Måtte kommentere sammenligning af frugt til farve for at undgå kompilatorfejl: // fejl: uforlignelige typer: Frugt og farve returnerer /*Fruit.WATERMELON == kandidatFarve * / falsk; } / ** * Angiv, om den leverede frugt er en jordbær ({@code true}) eller ej * ({@code false}). * * @param kandidatFrugt Frugt, der måske eller ikke er et jordbær; null er * helt acceptabelt (bring it on!). * @return {@code true}, hvis frugt er jordbær; {@code false} hvis * forudsat frugt IKKE er jordbær. * / public boolean isFruitStrawberry (Frugt kandidatFrugt) {return Fruit.STRAWBERRY == kandidatFrugt; } / ** * Angiv, om den leverede frugt er et hindbær ({@code true}) eller ej * ({@code false}). * * @param kandidatFrugt Frugt, der måske eller ikke er et hindbær; null er * fuldstændig og fuldstændig uacceptabel; bedes du ikke videregive null, tak, * tak, tak. * @return {@code true}, hvis frugt er hindbær; {@code false} hvis * forudsat frugt IKKE er hindbær. * / public boolean isFruitRaspberry (Frugt kandidatFrugt) {returner kandidatFrugt.equals (Frugt.RASPBERRY); } / ** * Angiv, om det angivne objekt er en frugt. RASPBERRY ({@code true}) eller * ikke ({@code false}). * * @param candidObject Objekt, der måske eller måske ikke er et hindbær og måske * eller måske ikke engang er en frugt! * @return {@code true} hvis det leveres Objekt er en frugt.RASPBERRY; {@code false} * hvis det ikke er en frugt eller ikke et hindbær. * / public boolean isObjectRaspberry (Object candidateObject) {return candidObject.equals (Fruit.RASPBERRY); } / ** * Angiv, om den leverede farve er et hindbær. Dette er fuldstændig vrøvl *, fordi en farve aldrig kan svare til en frugt, men kompilatoren tillader denne * kontrol, og kun en runtime-bestemmelse kan indikere, at de ikke er * lige, selvom de aldrig kan være lige. Sådan skal IKKE gøre ting. * * @param kandidatFarve farve, der aldrig bliver et hindbær. * @retur {@code false}. Altid. * / public boolean isColorRaspberry (java.awt.Color kandidatColor) {// // IKKE GØR DETTE: Spild af indsats og vildledende kode !!!!!!!! // returner Fruit.RASPBERRY.equals (candidColor); } / ** * Angiv, om den leverede frugt er en drue ({@code true}) eller ej * ({@code false}). * * @param kandidat Frugt Frugt, der måske eller ikke er en drue; null er * helt acceptabelt (bring it on!). * @return {@code true} hvis leveret frugt er en drue; {@code false} hvis * forudsat frugt IKKE er en drue. * / public boolean isFruitGrape (Frugt kandidatFrugt) {returner Frugt.GRAPE.equals (kandidatFrugt); }} 

Jeg besluttede at nærme demonstrationen af ​​de ideer, der er fanget i ovenstående metoder, via enhedstest. Især bruger jeg Groovys GroovyTestCase. Denne klasse til brug af Groovy-drevet enhedstest er i den næste kodeliste.

EnumComparisonTest.groovy

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