Ohjelmointi

Käytä == (tai! =) Verrata Java Enumsia

Useimmat uudet Java-kehittäjät oppivat nopeasti, että heidän tulisi yleensä verrata Java-merkkijonoja käyttämällä String.equals (Object) -toimintoa ==. Tätä korostetaan ja vahvistetaan uusille kehittäjille toistuvasti, koska he melkein aina tarkoittaa verrata merkkijonon sisältöä (merkkijonon muodostavat todelliset merkit) eikä merkkijonon identiteettiä (sen osoite muistissa). Väitän, että meidän pitäisi vahvistaa käsitystä siitä == voidaan käyttää Enum.equals (Object): n sijaan. Annan perustelun tälle väitteelle tämän viestin loppuosassa.

Mielestäni on neljä syytä == verrata Java enums on melkein aina parempi kuin "yhtä suuri" -menetelmä:

  1. == on enums tarjoaa saman odotetun vertailun (sisällön) kuin on yhtä suuri
  2. == on enums on luultavasti luettavampi (vähemmän sanallinen) kuin on yhtä suuri
  3. == on enums on turvallisempaa kuin on yhtä suuri
  4. == on enums tarjoaa käännösajan (staattisen) tarkistuksen ajonaikaisen tarkistuksen sijaan

Toinen yllä lueteltu syy ("luultavasti luettavampi") on tietysti mielipiteen asia, mutta siitä osasta "vähemmän sanallista" voidaan sopia. Ensimmäinen syy, jota yleensä pidän == kun vertaillaan enumeja, on seurausta siitä, miten Java Language Specification kuvaa enumeja. Luvun 8.9 ("Enums") mukaan:

On kääntöaikavirhe yrittää nimenomaisesti ilmentää enum-tyyppiä. Lopullinen kloonimenetelmä Enumissa varmistaa, että enum-vakioita ei voida koskaan kloonata, ja sarjallisuusmekanismin erityinen käsittely varmistaa, että päällekkäisiä instansseja ei koskaan luoda deserialisaation seurauksena. Enum-tyyppien heijastava ilmentäminen on kielletty. Yhdessä nämä neljä asiaa varmistavat, että enum -tyyppisiä esiintymiä ei ole enum-vakioiden määrittelemien lisäksi.

Koska kustakin enumvakiosta on vain yksi esiintymä, on sallittua käyttää == -operaattoria equals-menetelmän sijasta vertaamalla kahta objektiviittausta, jos tiedetään, että ainakin yksi niistä viittaa enum-vakioon. (Enumin Equals-menetelmä on lopullinen menetelmä, joka vain käyttää super.equals-argumenttia argumentissaan ja palauttaa tuloksen, jolloin identiteettivertailu suoritetaan.)

Ote edellä esitetystä spesifikaatiosta viittaa ja toteaa sitten nimenomaisesti, että sen käyttö on turvallista == operaattori vertaamaan kahta enumia, koska ei ole mitään tapaa, että samaa enumvakiota voi olla useampi kuin yksi esiintymä.

Neljäs etu == yli .erot kun vertaillaan enumeja, se liittyy käännösajan turvallisuuteen. Käyttö == pakottaa tiukemman kääntöajan tarkistuksen kuin .erot koska Object.equals (Object) on sopimuksella otettava mielivaltainen Esine. Käytettäessä staattisesti kirjoitettua kieltä, kuten Java, uskon hyödyntävän tämän staattisen kirjoittamisen etuja mahdollisimman paljon. Muuten käytän dynaamisesti kirjoitettua kieltä. Uskon, että yksi tehokkaan Java: n toistuvista teemoista on juuri tämä: mieluummin staattisen tyyppitarkistus aina kun mahdollista.

Oletetaan esimerkiksi, että minulla oli nimetty mukautettu enum Hedelmät ja yritin verrata sitä luokkaan java.awt.Color. Käyttämällä == operaattori sallii minun saada käännösongelma (mukaan lukien ennakkoilmoitus suosikkini Java IDE: ssä) ongelmasta. Tässä on koodiluettelo, joka yrittää verrata mukautettua enumia JDK-luokkaan käyttämällä == operaattori:

/ ** * Ilmoita, jos toimitetaan Väri on vesimeloni. * * Tämän menetelmän toteutus kommentoidaan, jotta vältetään kääntäjävirhe *, joka = = = oikeutetusti kieltää vertaamasta kahta objektia, jotka eivät ole ja * eivät voi olla sama asia koskaan. * * @param ehdokasVäri Väri, joka ei koskaan ole vesimeloni. * @return Pitäisi koskaan olla totta. * / public boolean isColorWatermelon (java.awt.Color kandidColor) {// Tämä hedelmien ja värien vertailu johtaa kääntäjävirheeseen: // virhe: vertailukelpoiset tyypit: Fruit and Color return Fruit.WATERMELON == ehdokasVäri; } 

Kääntäjävirhe näkyy seuraavassa näytön tilannekuvassa.

Vaikka en ole virheiden fani, pidän parempana, että ne jäävät staattisesti kääntöaikaan sen sijaan, että ne riippuvat ajonaikaisesta kattavuudesta. Olisinko käyttänyt on yhtä suuri Tätä vertailua varten koodi olisi koonnut hienosti, mutta menetelmä palaisi aina väärä väärä, koska ei ole mitään tapaa a dustin.esimerkkejä enum on yhtä suuri kuin a java.awt.väri luokassa. En suosittele sitä, mutta tässä on vertailumenetelmä .erot:

/ ** * Ilmoita, onko toimitettu väri vadelma. Tämä on täysin hölynpölyä *, koska väri ei voi koskaan olla yhtä suuri kuin hedelmä, mutta kääntäjä sallii tämän * tarkistuksen ja vain suorituksen määrittäminen voi osoittaa, etteivät ne ole * tasa-arvoiset, vaikka ne eivät koskaan voi olla yhtä suuria. Näin EI saa tehdä asioita. * * @param ehdokasVäri Väri, joka ei koskaan ole vadelma. * @return {@code false}. Aina. * / public boolean isColorRaspberry (java.awt.Color kandidColor) {// // ÄLÄ TEE TÄTÄ: Vaivaa ja harhaanjohtava koodi !!!!!!!! // return Fruit.RASPBERRY.equals (ehdokasväri); } 

Edellä mainitun "mukava" asia on kääntöaikavirheiden puuttuminen. Se kokoaa kauniisti. Valitettavasti tämä maksetaan mahdollisesti korkealla hinnalla.

Viimeinen etu, jonka käytin == mielummin kuin Enum. On yhtä suuri kun vertaillaan enumeja, on pelätyn NullPointerExceptionin välttäminen. Kuten totesin tehokkaassa Java NullPointerException -käsittelyssä, haluan yleensä välttää odottamattomia NullPointerExceptions. On olemassa rajoitettu joukko tilanteita, joissa haluan todella, että nollan olemassaoloa kohdellaan poikkeustapauksena, mutta usein pidän parempana ongelman siroavammasta raportoinnista. Etu vertailemalla laskelmia == on, että nollaa voidaan verrata ei-nollaan enumiin kohtaamatta a NullPointerException (NPE). Tämän vertailun tulos on tietysti väärä.

Yksi tapa välttää NPE käytön aikana .equals (objekti) on vedota on yhtä suuri menetelmä laskentavakiota tai tunnettua ei-nollaa enumia vastaan ​​ja välittää sitten kyseenalaisen luonteen (mahdollisesti nolla) potentiaalinen enum parametriin on yhtä suuri menetelmä. Tätä on usein tehty jo vuosia Javalla Stringsin avulla NPE: n välttämiseksi. Kuitenkin == operaattori, vertailun järjestyksellä ei ole merkitystä. Pidän siitä.

Olen esittänyt väitteeni ja nyt siirryn muutamiin koodiesimerkkeihin. Seuraava luettelo on toteutettu aiemmin mainittu hypoteettinen hedelmäluettelo.

Hedelmät.java

pakkaus pölyä.esimerkkejä; public enum Hedelmät {APPLE, BANANA, BLACKBERRY, BLUEBERRY, CHERRY, GRAPE, KIWI, MANGO, ORANGE, RASPBERRY, STRAWBERRY, TOMATO, WATERMELON} 

Seuraava koodiluettelo on yksinkertainen Java-luokka, joka tarjoaa menetelmiä sen havaitsemiseksi, onko tietty enumi tai esine tietty hedelmä. Laitoin tavallisesti tällaiset shekit itse enumiin, mutta ne toimivat paremmin erillisessä luokassa tässä havainnollistamistarkoituksiani varten. Tähän luokkaan sisältyy kaksi aikaisemmin esitettyä vertailumenetelmää Hedelmät että Väri molempien kanssa == ja on yhtä suuri. Tietenkin menetelmä == vertaamaan enumia luokkaan, kyseisen osan oli annettava kommentti, jotta se olisi oikein koottu.

EnumComparisonMain.java

pakkaus pölyä.esimerkkejä; public class EnumComparisonMain {/ ** * Ilmoita, onko toimitettu hedelmä vesimelonia ({@code true} vai ei * ({@code false}). * * @param ehdokasFruit Hedelmiä, jotka voivat olla vesimeloneja tai ei, nolla on * täysin hyväksyttävä (tuo se päälle!). * @return {@code true} jos hedelmä on vesimelonia; {@code false} jos * edellyttäen, että hedelmä EI OLE vesimeloni. * / public boolean isFruitWatermelon (Fruit ehdokasFruit) {return ehdokasFruit = = Fruit.WATERMELON;} / ** * Ilmoita, onko annettu objekti Fruit.WATERMELON ({@code true}) vai * ei ({@code false}). * * @Param ehdokasObject Objekti, joka voi olla tai ei vesimeloni ja ei * voi edes olla hedelmä! * @return {@code true}, jos annettu objekti on hedelmä.WATERMELON; * {@code false} jos annettu objekti ei ole Fruit.WATERMELON. * / public boolean isObjectWatermelon (Object ehdokasobjekti ) {return ehdokasobjekti == Hedelmä.VESIMERKKI;} / ** * Ilmoita, jos toimitetaan Väri on vesimeloni. * * Tämän menetelmän käyttöönottoa kommentoidaan Vältä kääntäjävirhettä *, joka kieltää == oikeutetusti vertaamasta kahta objektia, jotka eivät ole ja * eivät voi olla sama asia koskaan. * * @param ehdokasVäri Väri, joka ei koskaan ole vesimeloni. * @return Pitäisi koskaan olla totta. * / public boolean isColorWatermelon (java.awt.Color candidColor) {// Piti kommentoida hedelmien ja värien vertailua kääntäjävirheen välttämiseksi: // virhe: vertailukelpoiset tyypit: Hedelmien ja värien palautus /*Fruit.WATERMELON == ehdokasVäri * / väärä; } / ** * Ilmoita, onko toimitettu hedelmä mansikka ({@code true}) vai ei * ({@code false}). * * @param ehdokasFruit Fruit, joka voi olla mansikka; null on * täysin hyväksyttävä (ota se käyttöön!). * @return {@code true} jos hedelmä on mansikkaa; {@code false} jos * jos hedelmä EI OLE mansikka. * / public boolean isFruitStrawberry (Fruit ehdokasFruit) {return Fruit.STRAWBERRY == ehdokasFruit; } / ** * Ilmoita, onko toimitettu hedelmä vadelma ({@code true}) vai ei * ({@code false}). * * @param ehdokasFruit Fruit, joka voi olla vadelma tai ei; null on * täysin ja täysin mahdoton hyväksyä; älä välitä nollaa, kiitos, * kiitos, kiitos. * @return {@code true}, jos hedelmä on vadelma; {@code false} jos * jos hedelmä EI OLE vadelma. * / public boolean isFruitRaspberry (Fruit ehdokasFruit) {return ehdokasFruit.equals (Fruit.RASPBERRY); } / ** * Ilmoita, onko annettu objekti hedelmää.MÄNTELÖ ({@code true}) vai * ei ({@code false}). * * @param ehdokasObjekti Objekti, joka voi olla vadelma tai ei, ja voi * olla tai ei edes olla hedelmä! * @return {@code true}, jos Object on Fruit.RASPBERRY; {@code false} *, jos se ei ole hedelmä tai vadelma. * / public boolean isObjectRaspberry (Object kandidObject) {palauta ehdokasObject.equals (Fruit.RASPBERRY); } / ** * Ilmoita, onko toimitettu väri vadelma. Tämä on täysin hölynpölyä *, koska väri ei voi koskaan olla yhtä suuri kuin hedelmä, mutta kääntäjä sallii tämän * tarkistuksen ja vain suorituksen määrittäminen voi osoittaa, etteivät ne ole * tasa-arvoiset, vaikka ne eivät koskaan voi olla yhtä suuria. Näin EI saa tehdä asioita. * * @param ehdokasVäri Väri, joka ei koskaan ole vadelma. * @return {@code false}. Aina. * / public boolean isColorRaspberry (java.awt.Color kandidColor) {// // ÄLÄ TEE TÄTÄ: Vaivaa ja harhaanjohtava koodi !!!!!!!! // return Fruit.RASPBERRY.equals (ehdokasväri); } / ** * Ilmoita, onko toimitettu hedelmä rypäle ({@code true}) vai ei * ({@code false}). * * @param ehdokasFruit Hedelmät, jotka voivat olla rypäleitä tai eivät; null on * täysin hyväksyttävä (ota se käyttöön!). * @return {@code true}, jos hedelmä on rypäle; {@code false} jos * edellyttäen, että hedelmä EI OLE rypäle. * / julkinen totuusarvo onFruitGrape (Fruit ehdokasFruit) {return Fruit.GRAPE.equals (ehdokasFruit); }} 

Päätin lähestyä yllä olevien menetelmien ideoiden esittelyä yksikkötesteillä. Käytän erityisesti Groovyn GroovyTestCasea. Tämä luokka Groovy-käyttöisten yksiköiden testausta varten on seuraavassa koodiluettelossa.

EnumComparisonTest.groovy

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