Ohjelmointi

Java-perus hashCode ja on yhtä suuri kuin esittelyt

Haluan usein käyttää tätä blogia palatakseni ansaittuihin opintoihin Java-perusteista. Tämä blogiviesti on yksi tällainen esimerkki, ja siinä keskitytään havainnollistamaan yhtäläisten (Object) ja hashCode () -menetelmien takana olevaa vaarallista voimaa. En käsittele näiden kahden erittäin merkittävän menetelmän kaikkia vivahteita, jotka kaikilla Java-objekteilla on joko nimenomaisesti ilmoitettu tai implisiittisesti peritty vanhemmilta (mahdollisesti suoraan objektilta itseltään), mutta käsittelen joitain yleisiä ongelmia, jotka syntyvät, kun nämä ovat ei ole toteutettu tai niitä ei ole toteutettu oikein. Yritän myös osoittaa näillä esittelyillä, miksi on tärkeää huolellisessa koodin tarkistuksessa, perusteellisessa yksikötestauksessa ja / tai työkalupohjaisessa analyysissä näiden menetelmien toteutusten oikeellisuuden varmistamiseksi.

Koska kaikki Java-objektit perivät viime kädessä toteutukset on yhtä suuri (objekti) ja hash koodin(), Java-kääntäjä ja todellakin Java-ajonaikainen käynnistysohjelma eivät ilmoita ongelmasta, kun käytetään näiden menetelmien "oletustoteutuksia". Valitettavasti, kun näitä menetelmiä tarvitaan, näiden menetelmien oletustoteutuksia (kuten heidän serkkunsa toString-menetelmä) halutaan harvoin. Object-luokan Javadoc-pohjainen API-dokumentaatio käsittelee "sopimusta", jonka odotetaan toteutettavan on yhtä suuri (objekti) ja hash koodin() menetelmiä ja keskustellaan myös kunkin todennäköisestä oletustoteutuksesta, elleivät lapsiluokat korvaa niitä.

Tämän viestin esimerkeissä käytän HashAndEquals-luokkaa, jonka koodiluettelo näkyy erilaisten Henkilöluokkien objektikohteiden ilmentymien vieressä, joiden tuen taso on erilainen. hash koodin ja on yhtä suuri menetelmiä.

HashAndEquals.java

pakkaus pölyä.esimerkkejä; tuo java.util.HashSet; tuo java.util.Set; tuo staattinen java.lang.System.out; julkinen luokka HashAndEquals {private static final String HEADER_SEPARATOR = "=========================================== =============================== " yksityinen staattinen lopullinen int HEADER_SEPARATOR_LENGTH = HEADER_SEPARATOR.pituus (); yksityinen staattinen lopullinen merkkijono NEW_LINE = System.getProperty ("line.separator"); yksityinen lopullinen henkilö1 = uusi henkilö ("Flintstone", "Fred"); yksityinen lopullinen henkilö2 = uusi henkilö ("raunio", "Barney"); yksityinen lopullinen henkilöhenkilö3 = uusi henkilö ("Flintstone", "Fred"); yksityinen lopullinen henkilö henkilö = uusi henkilö ("raunio", "Barney"); public void displayContents () {printHeader ("KOHTEEN SISÄLTÖ"); out.println ("Henkilö 1:" + henkilö1); out.println ("Henkilö 2:" + henkilö2); out.println ("Henkilö 3:" + henkilö3); out.println ("Henkilö 4:" + henkilö4); } public void CompareEquality () {printHeader ("TASA-ARVO VERTAILUT"); out.println ("Henkilö1.yhtäläiset (Henkilö2):" + henkilö1.erät (henkilö2)); out.println ("Henkilö1.yhtäläiset (Henkilö3):" + henkilö1.erät (henkilö3)); out.println ("Henkilö2.yhtäläiset (Henkilö4):" + henkilö2.yhdenvertaiset (henkilö4)); } public void CompareHashCodes () {printHeader ("VERTAA HASH-KOODEJA"); out.println ("Henkilö1.hashCode ():" + henkilö1.hashCode ()); out.println ("Henkilö2.hashCode ():" + henkilö2.hashCode ()); out.println ("Henkilö3.hashCode ():" + henkilö3.hashCode ()); out.println ("Henkilö4.hashCode ():" + henkilö4.hashCode ()); } public set addToHashSet () {printHeader ("LISÄÄ ASETETTAVIA ASETTEITA - OVATKO LISÄTYTKÖ TAI SAMAA?"); lopullinen sarja = uusi HashSet (); out.println ("Aseta.add (henkilö1):" + sarja.add (henkilö1)); out.println ("Aseta.add (henkilö2):" + joukko.add (henkilö2)); out.println ("Aseta.add (henkilö3):" + set.add (henkilö3)); out.println ("Aseta.add (henkilö4):" + set.add (henkilö4)); paluu asettaa; } public void removeFromHashSet (final Set sourceSet) {printHeader ("POISTA ELEMIT ASETUKSESTA - VOITKO LÖYDÄN, ETTÄ POISTAA?"); out.println ("Set.remove (Person1):" + sourceSet.remove (person1)); out.println ("Set.remove (Person2):" + sourceSet.remove (person2)); out.println ("Set.remove (Person3):" + sourceSet.remove (person3)); out.println ("Set.remove (Person4):" + sourceSet.remove (person4)); } public static void printHeader (final String headerText) {out.println (NEW_LINE); out.println (HEADER_SEPARATOR); out.println ("=" + headerText); out.println (HEADER_SEPARATOR); } public static void main (final String [] argumentit) {final HashAndEquals-ilmentymä = new HashAndEquals (); instance.displayContents (); instance.compareEquality (); instance.compareHashCodes (); lopullinen joukko = esimerkki.addToHashSet (); out.println ("Aseta ennen poistoja:" + aseta); //instance.person1.setFirstName("Bam Bam "); instance.removeFromHashSet (sarja); out.println ("Aseta poistojen jälkeen:" + aseta); }} 

Yllä olevaa luokkaa käytetään toistuvasti sellaisenaan, ja vain yksi pieni muutos myöhemmin postissa. Kuitenkin Henkilö luokka muutetaan vastaamaan merkitystä on yhtä suuri ja hash koodin ja osoittaa, kuinka helppoa näiden sekoittaminen voi olla, mutta samalla on vaikea jäljittää ongelmaa virheen sattuessa.

Ei nimenomaista on yhtä suuri tai hash koodin Menetelmät

Ensimmäinen versio Henkilö luokka ei tarjoa nimenomaista ohitettua versiota kummastakaan on yhtä suuri menetelmä tai hash koodin menetelmä. Tämä osoittaa jokaisen perittyjen menetelmien "oletustoteutuksen" Esine. Tässä on lähdekoodi Henkilö ilman hash koodin tai on yhtä suuri nimenomaisesti ohitettu.

Person.java (ei nimenomaista hashCode- tai equals-menetelmää)

pakkaus pölyä.esimerkkejä; julkinen luokka Henkilö {yksityinen lopullinen Merkkijono sukunimi; yksityinen lopullinen String firstName; public Person (viimeinen merkkijono newLastName, viimeinen merkkijono newFirstName) {this.lastName = newLastName; this.esinimi = newFirstName; } @Override public String toString () {return this.firstName + "" + this.lastName; }} 

Tämä ensimmäinen versio Henkilö ei tarjoa get / set-menetelmiä eikä tarjoa on yhtä suuri tai hash koodin toteutukset. Kun pääesittelyluokka HashAndEquals suoritetaan tämän tapauksilla on yhtä suuri- ilman ja hash koodin-Vähemmän Henkilö luokassa, tulokset näkyvät seuraavan näytön tilannekuvan mukaisesti.

Edellä esitetystä tuloksesta voidaan tehdä useita havaintoja. Ensinnäkin ilman nimenomaista on yhtä suuri (objekti) -menetelmää, mikään Henkilö pidetään tasa-arvoisina, vaikka kaikki instanssien attribuutit (kaksi merkkijonoa) olisivat identtisiä. Tämä johtuu siitä, kuten oletuksena Object.equals (Object) -oppaassa selitetään on yhtä suuri toteutus perustuu tarkkaan vertailuarvoon:

Objekti-luokan Equals-menetelmä toteuttaa mahdollisimman erottelevan mahdollisen ekvivalenssisuhteen esineisiin; toisin sanoen millä tahansa muulla kuin nolla-viitearvolla x ja y tämä menetelmä palauttaa arvon tosi vain ja vain, jos x ja y viittaavat samaan objektiin (x == y: n arvo on tosi).

Toinen havainto tästä ensimmäisestä esimerkistä on, että hajautuskoodi on erilainen kullekin Henkilö objekti, vaikka kahdella instanssilla olisi samat arvot kaikille määritteilleen. HashSet palaa totta kun "ainutlaatuinen" objekti lisätään (HashSet.add) joukkoon tai väärä jos lisättyä objektia ei pidetä ainutlaatuisena eikä sitä lisätä. Samoin HashSetPoistomenetelmä palauttaa totta jos toimitettu esine katsotaan löydetyksi ja poistetuksi tai väärä jos määritetyn objektin ei katsota olevan osa HashSet joten sitä ei voida poistaa. Koska on yhtä suuri ja hash koodin Perityt oletusmenetelmät käsittelevät näitä esiintymiä täysin erilaisina, ei ole yllätys, että kaikki lisätään sarjaan ja kaikki poistetaan joukosta.

Selkeä on yhtä suuri Vain menetelmä

Toinen versio Henkilö luokka sisältää nimenomaisesti ohitetun on yhtä suuri menetelmä seuraavassa koodiluettelossa.

Person.java (tarjotaan yksiselitteinen menetelmä)

pakkaus pölyä.esimerkkejä; julkinen luokka Henkilö {yksityinen lopullinen Merkkijono sukunimi; yksityinen lopullinen String firstName; public Person (viimeinen merkkijono newLastName, viimeinen merkkijono newFirstName) {this.lastName = newLastName; this.esinimi = newFirstName; } @Override public boolean equals (Object obj) {if (obj == null) {return false; } if (tämä == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {palauta false; } lopullinen Henkilö muu = (Henkilö) obj; jos (this.sukunimi == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {palauttaa false; } if (this.esinimi == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {palauta false; } return true; } @Override public String toString () {return this.firstName + "" + this.lastName; }} 

Kun tämän tapauksia Henkilö kanssa on yhtä suuri (objekti) käytetään nimenomaisesti määriteltyjä, lähtö on seuraavan näytön tilannekuvan mukainen.

Ensimmäinen havainto on, että nyt on yhtä suuri kehottaa Henkilö tapaukset todellakin palaavat totta kun objekti on tasainen kaikkien ominaisuuksien ollessa samat sen sijaan, että tarkistat tarkan viite-tasa-arvon. Tämä osoittaa, että tapa on yhtä suuri täytäntöönpano Henkilö on tehnyt työnsä. Toinen havainto on, että on yhtä suuri menetelmä ei ole vaikuttanut kykyyn lisätä ja poistaa näennäisesti sama esine HashSet.

Selkeä on yhtä suuri ja hash koodin Menetelmät

Nyt on aika lisätä selkeä hash koodin() menetelmä Henkilö luokassa. Todellakin tämä olisi pitänyt tehdä, kun on yhtä suuri menetelmä otettiin käyttöön. Syy tähän on ilmoitettu Object.equals (Object) menetelmä:

Huomaa, että hashCode-menetelmä on yleensä tarpeen ohittaa aina, kun tämä menetelmä ohitetaan, jotta säilytetään hashCode-menetelmän yleissopimus, jonka mukaan samoilla esineillä on oltava samat hash-koodit.

Täällä on Henkilö nimenomaisesti toteutetun kanssa hash koodin menetelmä, joka perustuu samoihin attribuutteihin Henkilö kuten on yhtä suuri menetelmä.

Person.java (eksplisiittinen yhtälö ja hashCode-toteutukset)

pakkaus pölyä.esimerkkejä; julkinen luokka Henkilö {yksityinen lopullinen Merkkijono sukunimi; yksityinen lopullinen String firstName; public Person (viimeinen merkkijono newLastName, viimeinen merkkijono newFirstName) {this.lastName = newLastName; this.esinimi = newFirstName; } @Override public int hashCode () {return lastName.hashCode () + firstName.hashCode (); } @Override public boolean equals (Object obj) {if (obj == null) {return false; } if (tämä == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {palauta false; } lopullinen Henkilö muu = (Henkilö) obj; jos (this.sukunimi == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {palauttaa false; } if (this.esinimi == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {palauta false; } return true; } @Override public String toString () {return this.firstName + "" + this.lastName; }} 

Uuden käynnistyksen tulos Henkilö luokan kanssa hash koodin ja on yhtä suuri menetelmät esitetään seuraavaksi.

Ei ole yllättävää, että saman attribuutin arvoisten objektien palautetut hash-koodit ovat nyt samat, mutta mielenkiintoisempi havainto on, että voimme lisätä vain kaksi neljästä esiintymästä HashSet nyt. Tämä johtuu siitä, että kolmannen ja neljännen lisäysyrityksen katsotaan yrittävän lisätä objektia, joka oli jo lisätty joukkoon. Koska vain kaksi oli lisätty, vain kaksi voidaan löytää ja poistaa.

Mutable hashCode -attribuuttien ongelma

Tämän viestin neljännen ja viimeisen esimerkin osalta katson, mitä tapahtuu, kun hash koodin toteutus perustuu muuttuvaan määritteeseen. Tässä esimerkissä a setFirstName menetelmä lisätään Henkilö ja lopullinen muokkaus poistetaan etunimi määritteen. Lisäksi HashAndEquals-pääluokassa kommentti on poistettava riviltä, ​​joka kutsuu tätä uutta asetettua menetelmää. Uusi versio Henkilö näkyy seuraavaksi.

pakkaus pölyä.esimerkkejä; julkinen luokka Henkilö {yksityinen lopullinen Merkkijono sukunimi; yksityinen merkkijono etunimi; public Person (viimeinen merkkijono newLastName, viimeinen merkkijono newFirstName) {this.lastName = newLastName; this.esinimi = newFirstName; } @Override public int hashCode () {return lastName.hashCode () + firstName.hashCode (); } public void setFirstName (viimeinen merkkijono newFirstName) {this.firstName = newFirstName; } @Override public boolean equals (Object obj) {if (obj == null) {return false; } if (tämä == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {palauta false; } lopullinen Henkilö muu = (Henkilö) obj; jos (this.sukunimi == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {palauttaa false; } if (this.esinimi == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {palauta false; } return true; } @Override public String toString () {return this.firstName + "" + this.lastName; }} 

Seuraavassa näytetään tämän esimerkin suorittamisesta saatu tuotos.

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