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:
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 HashSet
Poistomenetelmä 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ä:
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.