Programmering

Bruk == (eller! =) Til å sammenligne Java Enums

De fleste nye Java-utviklere lærer raskt at de generelt bør sammenligne Java Strings ved hjelp av String.equals (Object) i stedet for å bruke ==. Dette blir vektlagt og forsterket til nye utviklere gjentatte ganger fordi de nesten alltid betyr å sammenligne strenginnhold (de faktiske tegnene som danner strengen) i stedet for strengens identitet (adressen i minnet). Jeg hevder at vi bør forsterke forestillingen om det == kan brukes i stedet for Enum.equals (Object). Jeg gir min begrunnelse for denne påstanden i resten av dette innlegget.

Det er fire grunner som jeg tror bruker == å sammenligne Java enums er nesten alltid å foretrekke fremfor å bruke "lik" -metoden:

  1. De == på enums gir samme forventede sammenligning (innhold) som er lik
  2. De == på enums er uten tvil mer leselig (mindre ordentlig) enn er lik
  3. De == på enums er mer null-trygt enn er lik
  4. De == på enums gir kompileringstid (statisk) kontroll i stedet for kjøretidskontroll

Den andre grunnen som er oppført ovenfor ("uten tvil mer lesbar") er åpenbart et meningsspørsmål, men den delen om "mindre verbose" kan bli enige om. Den første grunnen til at jeg generelt foretrekker det == når sammenligning av enums er en konsekvens av hvordan Java Language Specification beskriver enums. Avsnitt 8.9 ("Enums") sier:

Det er en kompilasjonsfeil å forsøke å eksplisitt starte en enum-type. Den endelige klonmetoden i Enum sikrer at enumkonstanter aldri kan klones, og den spesielle behandlingen med serialiseringsmekanismen sørger for at duplikatforekomster aldri blir opprettet som et resultat av deserialisering. Reflekterende instantiering av enumtyper er forbudt. Til sammen sørger disse fire tingene for at ingen forekomster av enumtype eksisterer utover de som er definert av enumkonstantene.

Fordi det bare er en forekomst av hver enumkonstant, er det tillatt å bruke == -operatøren i stedet for likemetoden når man sammenligner to objektreferanser hvis det er kjent at minst en av dem refererer til en enumkonstant. (Likemetoden i Enum er en endelig metode som bare påkaller super. Lik på argumentet og returnerer resultatet, og dermed utfører en identitetssammenligning.)

Utdraget fra spesifikasjonen vist ovenfor antyder og sier deretter eksplisitt at det er trygt å bruke == operatøren for å sammenligne to enums fordi det ikke er noen måte at det kan være mer enn en forekomst av samme enumkonstant.

Den fjerde fordelen til == over .er lik når man sammenligner enums har å gjøre med kompileringstidssikkerhet. Bruken av == tvinger en strengere kompileringstidskontroll enn den for .er lik fordi Object.equals (Object) etter kontrakt må ta vilkårlig Gjenstand. Når jeg bruker et statisk skrevet språk som Java, tror jeg på å utnytte fordelene med denne statiske typen så mye som mulig. Ellers vil jeg bruke et dynamisk skrevet språk. Jeg tror at et av de gjentatte temaene i Effektiv Java er nettopp det: foretrekker statisk typekontroll når det er mulig.

Anta for eksempel at jeg hadde et tilpasset enum kalt Frukt og jeg prøvde å sammenligne det med klassen java.awt.Color. Bruker == operatøren lar meg få en kompilasjonsfeil (inkludert forhåndsvarsel i min favoritt Java IDE) om problemet. Her er en kodeliste som prøver å sammenligne en tilpasset enum til en JDK-klasse ved hjelp av == operatør:

/ ** * Angi hvis gitt Farge er en vannmelon. * * Denne metodens implementering er kommentert for å unngå en kompilatorfeil * som legitimt tillater == å sammenligne to objekter som ikke er og * ikke kan være den samme. * * @param kandidat Farge Farge som aldri blir en vannmelon. * @return Bør aldri være sant. * / public boolean isColorWatermelon (java.awt.Color candidColor) {// Denne sammenligningen av frukt til farge vil føre til kompilatorfeil: // feil: uforlignelige typer: frukt og farge returnerer frukt.WATERMELON == kandidatfarge; } 

Kompilatorfeilen vises på skjermbildet som kommer neste.

Selv om jeg ikke er noen fan av feil, foretrekker jeg at de blir fanget statisk på kompileringstid i stedet for avhengig av kjøretidsdekning. Hadde jeg brukt er lik metode for denne sammenligningen, ville koden ha samlet seg fint, men metoden ville alltid komme tilbake falsk falsk fordi det er ingen måte a dustin. eksempler. frukt enum vil være lik a java.awt.Color klasse. Jeg anbefaler det ikke, men her er sammenligningsmetoden som brukes .er lik:

/ ** * Angi om gitt Farge er bringebær. Dette er fullstendig tull * fordi en farge aldri kan være lik en frukt, men kompilatoren tillater denne * sjekken, og bare en kjøretidsbestemmelse kan indikere at de ikke er * like selv om de aldri kan være like. Dette er hvordan IKKE å gjøre ting. * * @param kandidat Farge Farge som aldri blir et bringebær. * @return {@code false}. Alltid. * / public boolean isColorRaspberry (java.awt.Color kandidatColor) {// // IKKE GJØR DETTE: Sløsing med innsats og villedende kode !!!!!!!! // returner Fruit.RASPBERRY.equals (kandidatFarge); } 

Det "fine" med det ovennevnte er mangelen på kompileringsfeil. Den kompilerer vakkert. Dessverre betales dette med en potensielt høy pris.

Den endelige fordelen jeg listet med å bruke == heller enn Enum. Lik når man sammenligner enums, er det å unngå det fryktede NullPointerException. Som jeg sa i Effektiv Java NullPointerException Handling, liker jeg generelt å unngå uventet NullPointerExceptions. Det er et begrenset sett med situasjoner der jeg virkelig vil at eksistensen av en null skal behandles som et eksepsjonelt tilfelle, men ofte foretrekker jeg en mer grasiøs rapportering av et problem. En fordel ved å sammenligne enums med == er at en null kan sammenlignes med en ikke-null enum uten å møte en NullPointerException (NPE). Resultatet av denne sammenligningen er tydeligvis det falsk.

En måte å unngå NPE når du bruker .equals (Object) er å påkalle er lik metoden mot en enumkonstant eller en kjent ikke-null enum og deretter overføre den potensielle enum av tvilsom karakter (muligens null) som en parameter til er lik metode. Dette har ofte blitt gjort i mange år i Java med Strings for å unngå NPE. Imidlertid med == operatør, ikke sammenligningsrekkefølge noe. Jeg liker det.

Jeg har lagt fram argumentene mine, og nå går jeg videre til noen kodeeksempler. Den neste oppføringen er en realisering av den hypotetiske Fruit enum som er nevnt tidligere.

Frukt.java

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

Den neste kodelisten er en enkel Java-klasse som gir metoder for å oppdage om et bestemt enum eller objekt er en viss frukt. Jeg vil normalt sette sjekker som disse i selve enummet, men de fungerer bedre i en egen klasse her for mine illustrative og demonstrative formål. Denne klassen inkluderer de to metodene som er vist tidligere for sammenligning Frukt til Farge med begge == og er lik. Selvfølgelig, metoden ved hjelp av == for å sammenligne en enum til en klasse måtte den delen kommenteres for å kompilere ordentlig.

EnumComparisonMain.java

pakke dustin. eksempler; offentlig klasse EnumComparisonMain {/ ** * Angi om gitt frukt er en vannmelon ({@code true} eller ikke * ({@code false)). * * @param kandidat Frukt Frukt som kanskje eller ikke kan være en vannmelon; null er * helt akseptabelt (ta det videre!). * @return {@code true} hvis gitt frukt er vannmelon; {@code false} hvis * gitt frukt IKKE er en vannmelon. * / public boolean isFruitWatermelon (Fruit kandidatFrukt) {retur kandidatFrukt = = Fruit.WATERMELON;} / ** * Angi om det oppgitte objektet er en Fruit.WATERMELON ({@code true}) eller * ikke ({@code false)). * * @Param candidObject Object som kan eller ikke kan være et vannmelon og kan * ikke en gang være en frukt! * @return {@code true} hvis det oppgitte objektet er en Fruit.WATERMELON; * {@code false} hvis det oppgitte objektet ikke er Fruit.WATERMELON. ) {return candidObject == Fruit.WATERMELON;} / ** * Angi om det er gitt Farge er en vannmelon. * * Denne metodens implementering er kommentert til unngå en kompilatorfeil * som legitimt tillater == å sammenligne to objekter som ikke er og * ikke kan være den samme. * * @param kandidat Farge Farge som aldri blir en vannmelon. * @return Bør aldri være sant. * / public boolean isColorWatermelon (java.awt.Color candidColor) {// Måtte kommentere sammenligning av frukt til farge for å unngå kompilatorfeil: // feil: uforlignelige typer: frukt og farge returnerer /*Fruit.WATERMELON == kandidatfarge * / falsk; } / ** * Angi om gitt frukt er et jordbær ({@code true}) eller ikke * ({@code false}). * * @param kandidat Frukt Frukt som kanskje eller ikke er et jordbær; null er * helt akseptabelt (fortsett!). * @return {@code true} hvis gitt frukt er jordbær; {@code false} hvis * gitt frukt IKKE er jordbær. * / public boolean isFruitStrawberry (Fruit candidFruit) {return Fruit.STRAWBERRY == kandidatfrukt; } / ** * Angi om gitt frukt er et bringebær ({@code true}) eller ikke * ({@code false}). * * @param kandidat Frukt Frukt som kanskje eller ikke er et bringebær; null er * fullstendig uakseptabelt; Vennligst ikke pass null, vær så snill, * vær så snill. * @return {@code true} hvis frukten er bringebær; {@code false} hvis * gitt frukt IKKE er bringebær. * / public boolean isFruitRaspberry (Fruit candidateFruit) {return candidFruit.equals (Fruit.RASPBERRY); } / ** * Angi om det oppgitte objektet er en frukt.RASPBERRY ({@code true}) eller * ikke ({@code false}). * * @param kandidatObjekt Objekt som kanskje eller ikke er et bringebær og kan * eller ikke en gang er en frukt! * @return {@code true} hvis gitt Objekt er en frukt.RASPBERRY; {@code false} * hvis det ikke er en frukt eller ikke en bringebær. * / public boolean isObjectRaspberry (Object candidateObject) {return candidObject.equals (Fruit.RASPBERRY); } / ** * Angi om gitt farge er et bringebær. Dette er fullstendig tull * fordi en farge aldri kan være lik en frukt, men kompilatoren tillater denne * sjekken, og bare en kjøretidsbestemmelse kan indikere at de ikke er * like selv om de aldri kan være like. Dette er hvordan IKKE å gjøre ting. * * @param kandidat Farge Farge som aldri blir et bringebær. * @return {@code false}. Alltid. * / public boolean isColorRaspberry (java.awt.Color candidColor) {// // IKKE GJØR DETTE: Sløsing med innsats og villedende kode !!!!!!!! // returner Fruit.RASPBERRY.equals (kandidatFarge); } / ** * Angi om gitt frukt er en drue ({@code true}) eller ikke * ({@code false}). * * @param kandidat Frukt Frukt som kanskje eller ikke er en drue; null er * helt akseptabelt (fortsett!). * @return {@code true} hvis gitt frukt er en drue; {@code false} hvis * gitt frukt IKKE er en drue. * / public boolean isFruitGrape (Fruit candidateFruit) {return Fruit.GRAPE.equals (candidFruit); }} 

Jeg bestemte meg for å nærme meg demonstrasjon av ideene fanget i metodene ovenfor via enhetstester. Spesielt bruker jeg Groovy's GroovyTestCase. Denne klassen for bruk av Groovy-drevet enhetstesting er i neste kodeliste.

EnumComparisonTest.groovy

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