Programiranje

Osnovna hashCode Java in enaka Demonstracije

Ta blog pogosto uporabljam za ponovni ogled težko prisluženih lekcij o osnovah Jave. Ta objava v blogu je en tak primer in se osredotoča na ponazoritev nevarne moči, ki stoji za metodama equals (Object) in hashCode (). Ne bom zajemal vseh odtenkov teh dveh zelo pomembnih metod, ki jih imajo vsi objekti Java, ne glede na to, ali so izrecno deklarirani ali implicitno podedovani od starša (morda neposredno od samega predmeta), vendar bom zajemal nekaj pogostih težav, ki se pojavijo, ko se ne izvajajo ali se ne izvajajo pravilno. S temi predstavitvami poskušam pokazati tudi, zakaj je za natančen pregled kode, temeljito testiranje enot in / ali analizo, ki temelji na orodjih, pomembno preveriti pravilnost izvedb teh metod.

Ker vsi predmeti Java na koncu podedujejo izvedbe za je enako (objekt) in hashCode (), prevajalnik Java in izvajalnik Java Runtime zagotovo ne bo poročal o težavah pri klicu teh "privzetih izvedb" teh metod. Ko so te metode na žalost, so privzete izvedbe teh metod (na primer njihov bratranec metoda toString) le redko zaželene. Dokumentacija API-ja na osnovi Javadoc za razred Object obravnava "pogodbo", ki se pričakuje od kakršne koli izvedbe je enako (objekt) in hashCode () metode in razpravlja tudi o verjetni privzeti izvedbi vsake, če je ne preglasijo podrejeni razredi.

Za primere v tej objavi bom uporabil razred HashAndEquals, katerega seznam kode je prikazan poleg procesnih primerkov objektov različnih razredov Person z različnimi stopnjami podpore za hashCode in enako metode.

HashAndEquals.java

paket prah v.primeri; uvoz java.util.HashSet; import java.util.Set; uvoz statičnega java.lang.System.out; javni razred HashAndEquals {zasebni statični končni niz HEADER_SEPARATOR = "======================================= =============================== ""; zasebni statični končni int HEADER_SEPARATOR_LENGTH = HEADER_SEPARATOR.length (); zasebni statični končni niz NEW_LINE = System.getProperty ("line.separator"); zasebna končna oseba person1 = nova oseba ("Flintstone", "Fred"); zasebna končna oseba person2 = nova oseba ("Rubble", "Barney"); zasebna končna oseba person3 = nova oseba ("Flintstone", "Fred"); zasebna končna oseba person4 = nova oseba ("Rubble", "Barney"); public void displayContents () {printHeader ("VSEBINA PREDMETOV"); out.println ("Oseba 1:" + oseba1); out.println ("Oseba 2:" + oseba2); out.println ("Oseba 3:" + oseba3); out.println ("Oseba 4:" + oseba4); } public void compareEquality () {printHeader ("PRIMERJAVE ENAKOSTI"); out.println ("Person1.equals (Person2):" + person1.equals (person2)); out.println ("Person1.equals (Person3):" + person1.equals (person3)); out.println ("Person2.equals (Person4):" + person2.equals (person4)); } public void compareHashCodes () {printHeader ("PRIMERJAJ RAZMERJENE KODE"); out.println ("Person1.hashCode ():" + person1.hashCode ()); out.println ("Person2.hashCode ():" + person2.hashCode ()); out.println ("Person3.hashCode ():" + person3.hashCode ()); out.println ("Person4.hashCode ():" + person4.hashCode ()); } public Set addToHashSet () {printHeader ("DODAJ ELEMENTE ZA NASTAVITEV - SO DODANI ALI ISTI?"); končni set set = nov HashSet (); out.println ("Set.add (Person1):" + set.add (person1)); out.println ("Set.add (Person2):" + set.add (person2)); out.println ("Set.add (Person3):" + set.add (person3)); out.println ("Set.add (Person4):" + set.add (person4)); povratni komplet; } public void removeFromHashSet (final Set sourceSet) {printHeader ("ODSTRANI ELEMENTE IZ NARAVE - ALI SO LAHKO NAJDENI ODSTRANITI?"); 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)); } javna statična void printHeader (končni niz headerText) {out.println (NEW_LINE); out.println (HEADER_SEPARATOR); out.println ("=" + headerText); out.println (HEADER_SEPARATOR); } public static void main (final String [] argument) {final HashAndEquals instance = new HashAndEquals (); instance.displayContents (); instance.compareEquality (); instance.compareHashCodes (); končni Set set = instance.addToHashSet (); out.println ("Set Before Removals:" + set); //instance.person1.setFirstName("Bam Bam "); instance.removeFromHashSet (set); out.println ("Set After Removals:" + set); }} 

Zgornji razred bo večkrat uporabljen takšen, kot je, z le eno manjšo spremembo kasneje v objavi. Vendar pa Oseba razred bo spremenjen, da bo odražal pomen enako in hashCode in pokazati, kako enostavno je lahko to zamočiti, hkrati pa je težko izslediti težavo, ko pride do napake.

Brez eksplicitnega enako ali hashCode Metode

Prva različica Oseba razred ne vsebuje izrecno razveljavljene različice enako metoda ali hashCode metoda. To bo pokazalo "privzeto izvajanje" vsake od teh metod, podedovanih od Predmet. Tu je izvorna koda za Oseba brez hashCode ali enako izrecno razveljavljeno.

Person.java (brez eksplicitne hashCode ali enake metode)

paket prah v.primeri; javni razred Oseba {private final String lastName; zasebni končni niz firstName; javna oseba (končni niz newLastName, končni niz newFirstName) {this.lastName = newLastName; this.firstName = newFirstName; } @Override public String toString () {return this.firstName + "" + this.lastName; }} 

Ta prva različica Oseba ne zagotavlja metod get / set in ne zagotavlja enako ali hashCode izvedb. Ko je glavni predstavitveni razred HashAndEquals se izvede s primerki tega enako-brez in hashCode-brez Oseba razreda se rezultati prikažejo, kot je prikazano na naslednjem posnetku zaslona.

Iz zgoraj prikazanega rezultata je mogoče pridobiti več opažanj. Prvič, brez izrecne izvedbe je enako (objekt) metoda, nobeden od primerov Oseba se štejejo za enake, tudi če so vsi atributi primerkov (dva niza) enaki. Kot je razloženo v dokumentaciji za Object.equals (Object), je privzeta vrednost enako izvedba temelji na natančnem referenčnem ujemanju:

Metoda equals za razred Object izvaja na objektih najbolj diskriminacijsko razmerje enakovrednosti; to pomeni, da za vse neveljavne referenčne vrednosti x in y ta metoda vrne true, če in samo, če se x in y nanašata na isti objekt (x == y ima vrednost true).

Druga ugotovitev iz tega prvega primera je, da se hash koda razlikuje za vsak primerek Oseba objekt, tudi če imata dva primerka enake vrednosti za vse svoje atribute. Vrne se HashSet prav ko je naboru dodan "unikatni" objekt (HashSet.add) oz napačno če se dodani predmet ne šteje za edinstvenega in zato ni dodan. Podobno je HashSetvrne metoda odstranitve prav če se predvideni predmet šteje za najdenega in odstranjenega oz napačno če se šteje, da navedeni objekt ni del HashSet in ga zato ni mogoče odstraniti. Zaradi enako in hashCode podedovane privzete metode te primere obravnavajo kot popolnoma drugačne, zato ni presenetljivo, da so vsi dodani v nabor in vsi uspešno odstranjeni iz nabora.

Izrecno enako Samo metoda

Druga različica Oseba razred vključuje izrecno razveljavljeno enako metoda, kot je prikazano v naslednjem seznamu kod.

Person.java (na voljo je izrecna enaka metoda)

paket prah v.primeri; javni razred Oseba {private final String lastName; zasebni končni niz firstName; javna oseba (končni niz newLastName, končni niz newFirstName) {this.lastName = newLastName; this.firstName = newFirstName; } @Override public boolean equals (Object obj) {if (obj == null) {return false; } if (this == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {return false; } končna oseba druga = (oseba) obj; if (this.lastName == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {return false; } if (this.firstName == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {return false; } vrni true; } @Override public String toString () {return this.firstName + "" + this.lastName; }} 

Ko primeri tega Oseba s je enako (objekt) uporabljeni izrecno, izhod je tak, kot je prikazano na naslednjem posnetku zaslona.

Prvo opažanje je, da zdaj enako poziva Oseba primeri se resnično vrnejo prav kadar je objekt enak glede na to, da so vsi atributi enaki, namesto da bi preverjali, ali obstaja enaka referenca. To dokazuje, da je običaj enako izvajanje na Oseba je opravil svoje delo. Druga ugotovitev je, da je izvajanje enako metoda ni vplivala na zmožnost dodajanja in odstranjevanja na videz istega predmeta v datoteko HashSet.

Izrecno enako in hashCode Metode

Zdaj je čas, da dodate eksplicitno hashCode () metoda do Oseba razred. Dejansko bi bilo to res treba storiti, ko enako metoda je bila izvedena. Razlog za to je naveden v dokumentaciji za Object.equals (Object) metoda:

Upoštevajte, da je običajno treba preglasiti metodo hashCode, kadar koli je ta metoda razveljavljena, da se ohrani splošna pogodba za metodo hashCode, ki določa, da morajo imeti enaki predmeti enake hash kode.

Tukaj je Oseba z izrecno izvedenim hashCode metoda, ki temelji na istih lastnostih Oseba kot enako metoda.

Person.java (eksplicitne enake in izvedbe hashCode)

paket prah v.primeri; javni razred Oseba {private final String lastName; zasebni končni niz firstName; javna oseba (končni niz newLastName, končni niz newFirstName) {this.lastName = newLastName; this.firstName = newFirstName; } @Override public int hashCode () {return lastName.hashCode () + firstName.hashCode (); } @Override public boolean equals (Object obj) {if (obj == null) {return false; } if (this == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {return false; } končna oseba druga = (oseba) obj; if (this.lastName == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {return false; } if (this.firstName == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {return false; } vrni true; } @Override public String toString () {return this.firstName + "" + this.lastName; }} 

Rezultat izvajanja z novim Oseba razred s hashCode in enako naslednje metode.

Ni presenetljivo, da so hash kode, ki se vrnejo za predmete z enakimi vrednostmi atributov, zdaj enake, bolj zanimivo pa je opazovanje, da lahko dodamo le dva od štirih primerkov HashSet zdaj. Razlog za to je, da se pri tretjem in četrtem poskusu dodajanja poskuša dodati objekt, ki je bil že dodan v nabor. Ker sta bila dodana samo dva, je mogoče najti in odstraniti le dva.

Težave z spremenljivimi atributi hashCode

Za četrti in zadnji primer v tej objavi pogledam, kaj se zgodi, ko hashCode izvedba temelji na atributu, ki se spremeni. V tem primeru: a setFirstName je dodana metoda Oseba in dokončno modifikator je odstranjen iz ime atribut. Poleg tega mora glavni razred HashAndEquals odstraniti komentar iz vrstice, ki prikliče to novo nastavljeno metodo. Nova različica Oseba je prikazan naslednji.

paket prah v.primeri; javni razred Oseba {private final String lastName; private String firstName; javna oseba (končni niz newLastName, končni niz newFirstName) {this.lastName = newLastName; this.firstName = newFirstName; } @Override public int hashCode () {return lastName.hashCode () + firstName.hashCode (); } javna praznina setFirstName (končni niz newFirstName) {this.firstName = newFirstName; } @Override public boolean equals (Object obj) {if (obj == null) {return false; } if (this == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {return false; } končna oseba druga = (oseba) obj; if (this.lastName == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {return false; } if (this.firstName == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {return false; } vrni true; } @Override public String toString () {return this.firstName + "" + this.lastName; }} 

Izhod, ustvarjen z izvajanjem tega primera, je prikazan naprej.

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