Programavimas

Pagrindinis Java hashCode ir lygus demonstracijoms

Aš dažnai mėgstu naudoti šį tinklaraštį, kad galėčiau peržiūrėti sunkiai uždirbtas „Java“ pagrindų pamokas. Šis tinklaraščio įrašas yra vienas tokių pavyzdžių, kuriame daugiausia dėmesio skiriama pavojingų jėgų, slypinčių už metodų „lygūs“ („Objektas“) ir „hashCode“ (), iliustracijai. Neaptarsiu visų šių dviejų labai reikšmingų metodų, kuriuos visi „Java“ objektai turi, tiesiogiai deklaruojamus ar netiesiogiai paveldėtus iš tėvų (galbūt tiesiogiai iš paties objekto), niuansų, tačiau apžvelgsiu keletą dažniausiai pasitaikančių problemų, kurios kyla, kai jos yra nėra įgyvendinami arba nėra tinkamai įgyvendinami. Taip pat šiomis demonstracijomis bandau parodyti, kodėl svarbu atidžiai tikrinti kodus, atlikti išsamius vienetų bandymus ir (arba) įrankiais pagrįstą analizę, siekiant patikrinti šių metodų įdiegimo teisingumą.

Nes visi „Java“ objektai galiausiai paveldi diegimus lygus (objektas) ir hashCode (), „Java“ kompiliatorius ir iš tikrųjų „Java“ vykdymo laiko paleidimo priemonė nepraneša apie jokias problemas, kai naudojate šiuos „numatytuosius šių metodų diegimus“. Deja, kai reikia šių metodų, numatytieji šių metodų įgyvendinimai (pvz., Jų pusbrolis toString metodas) yra retai pageidaujami. „Javadoc“ pagrįstoje „Object“ klasės API dokumentacijoje aptariama „sutartis“, kurios tikimasi įgyvendinant bet kokį „ lygus (objektas) ir hashCode () metodus, taip pat aptariamas galimas numatytasis kiekvieno iš jų įgyvendinimas, jei to nepaisys vaikų klasės.

Šio įrašo pavyzdžiuose naudosiu „HashAndEquals“ klasę, kurios kodų sąrašas rodomas šalia įvairių Asmens klasių, skirtingo palaikymo lygio, objektų pavyzdžių. hashCode ir lygu metodai.

HashAndEquals.java

pakuotė dustin.pavyzdžiai; importuoti java.util.HashSet; importuoti java.util.Set; importuoti statinį java.lang.System.out; viešoji klasė „HashAndEquals“ {privati ​​statinė finalinė eilutė HEADER_SEPARATOR = "=========================================== =============================== "; privatus statinis galutinis int HEADER_SEPARATOR_LENGTH = HEADER_SEPARATOR.length (); privati ​​statinė galutinė eilutė NEW_LINE = System.getProperty ("line.separator"); privatus galutinis asmuo asmuo1 = naujas asmuo ("Flintstone", "Fred"); privatus galutinis asmuo asmuo2 = naujas asmuo ("griuvėsiai", "Barney"); privatus galutinis asmuo asmuo3 = naujas asmuo ("Flintstone", "Fred"); privatus galutinis asmuo asmuo4 = naujas asmuo ("griuvėsiai", "Barney"); public void displayContents () {printHeader ("OBJEKTŲ TURINYS"); out.println ("Asmuo 1:" + asmuo1); out.println ("2 asmuo:" + asmuo2); out.println ("Asmuo 3:" + asmuo3); out.println ("Asmuo 4:" + asmuo4); } public void CompareEquality () {printHeader ("LYGYBĖS PALYGINIMAI"); out.println ("Asmuo1.lygūs (Asmuo2):" + asmuo1.lygūs (asmuo2)); out.println ("Asmuo1.lygūs (Asmuo3):" + asmuo1.lygūs (asmuo3)); out.println ("Asmuo2.lygūs (Asmuo4):" + asmuo2.lygūs (asmuo4)); } public void CompareHashCodes () {printHeader ("PALYGINTI HASH KODUS"); out.println ("Asmuo1.hashCode ():" + asmuo1.hashCode ()); out.println ("Asmuo2.hashCode ():" + asmuo2.hashCode ()); out.println ("Asmuo3.hashCode ():" + asmuo3.hashCode ()); out.println ("Asmuo4.hashCode ():" + asmuo4.hashCode ()); } public Set addToHashSet () {printHeader ("PRIDĖTI NUSTATAMUS ELEMENTUS - AR JIE PRIDĖTI, ARBA PAT?"); galutinis rinkinio rinkinys = naujas „HashSet“ (); out.println ("Nustatyti.add (asmuo1):" + rinkinys.add (asmuo1)); out.println ("Nustatyti.add (asmuo2):" + rinkinys.add (asmuo2)); out.println ("Nustatyti.add (Asmuo3):" + rinkinys.add (asmuo3)); out.println ("Nustatyti.add (asmuo4):" + rinkinys.add (asmuo4)); grąžinimo rinkinys; } public void removeFromHashSet (final Set sourceSet) {printHeader ("PAŠALINKITE ELEMENTUS IŠ RINKINIO - AR JIE RASTI PAŠALINAMI?"); out.println ("Nustatyti.pašalinti (Asmuo1):" + šaltinisNustatyti.pašalinti (asmuo1)); out.println ("Nustatyti.pašalinti (Asmuo2):" + šaltinisNustatyti.Pašalinti (asmuo2)); out.println ("Nustatyti.pašalinti (Asmuo3):" + sourceSet.remove (asmuo3)); out.println ("Nustatyti.pašalinti (Asmuo4):" + šaltinisNustatyti.Pašalinti (asmuo4)); } 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 [] argumentai) {final HashAndEquals egzempliorius = new HashAndEquals (); instance.displayContents (); instance.compareEquality (); instance.compareHashCodes (); galutinis rinkinys = instance.addToHashSet (); out.println ("Nustatyti prieš pašalinimą:" + rinkinys); //instance.person1.setFirstName("Bam Bam "); instance.removeFromHashSet (rinkinys); out.println ("Nustatyti po pašalinimo:" + rinkinys); }} 

Aukščiau paminėta klasė bus naudojama tokia, kokia yra, vėliau įraše tik vienas nedidelis pakeitimas. Tačiau Asmuo klasė bus pakeista, kad atspindėtų lygu ir hashCode ir parodyti, kaip lengvai gali juos sujaukti, tuo pat metu sunku susekti problemą, kai yra klaida.

Nėra aiškaus lygu arba hashCode Metodai

Pirmoji Asmuo klasėje nepateikiama aiškiai nepaisoma nei vieno, nei kito lygu metodas arba hashCode metodas. Tai parodys kiekvieno iš šių paveldėtų metodų „numatytąjį įgyvendinimą“ Objektas. Čia yra šaltinio kodas Asmuo be hashCode arba lygu aiškiai nepaisoma.

Person.java (nėra aiškaus hashCode arba lygus metodo)

pakuotė dustin.pavyzdžiai; public class Asmuo {private final Stygos pavardė; privati ​​finalinė eilutė firstName; viešasis asmuo (final String newLastName, final String newFirstName) {this.lastName = newLastName; this.firstName = newFirstName; } @Paisyti viešosios eilutės toString () {grąžinti šį.vardas + "" + tai.pavardė; }} 

Ši pirmoji versija Asmuo nepateikia „get / set“ metodų ir nepateikia lygu arba hashCode įgyvendinimai. Kai pagrindinė parodomoji klasė „HashAndEquals“ vykdomas su to egzemplioriais lygu-bet ir hashCode-bet Asmuo klasėje, rezultatai rodomi taip, kaip parodyta kitoje ekrano nuotraukoje.

Iš aukščiau pateikto rezultato galima padaryti keletą pastebėjimų. Pirma, aiškiai neįgyvendinant lygus (objektas) metodas, nė vienas iš Asmuo yra laikomi vienodais, net jei visi egzempliorių (dviejų eilučių) atributai yra vienodi. Taip yra todėl, kad, kaip paaiškinta Object.equals (Object) dokumentuose, numatytasis lygu diegimas pagrįstas tikslia atskaitos atitiktimi:

Objektų klasės „equals“ metodas įgyvendina kuo labiau išskiriantį objektų lygiavertiškumo santykį; tai yra bet kurioms nulio nulinėms pamatinėms reikšmėms x ir y šis metodas grąžinamas tiesa tik tada, jei x ir y nurodo tą patį objektą (x == y turi true reikšmę).

Antras pastebėjimas iš šio pirmojo pavyzdžio yra tas, kad maišos kodas kiekvienam „ Asmuo objektas, net jei dviem egzemplioriais yra vienodos visų jų atributų vertės. Grįžta „HashSet“ tiesa kai prie rinkinio pridedamas „unikalus“ objektas (HashSet.add) arba melagingas jei pridėtas objektas nėra laikomas unikaliu ir todėl nėra pridedamas. Panašiai „HashSet“grąžinamas pašalinimo metodas tiesa jei laikoma, kad pateiktas objektas yra rastas ir pašalintas, arba melagingas jei laikoma, kad nurodytas objektas nėra „HashSet“ todėl jų negalima pašalinti. Nes lygu ir hashCode paveldėti numatytieji metodai traktuoja šiuos atvejus kaip visiškai skirtingus, nenuostabu, kad visi pridedami prie rinkinio ir visi sėkmingai pašalinami iš rinkinio.

Aiškus lygu Tik metodas

Antroji versija Asmuo klasėje yra aiškiai nepaisoma lygu metodas, kaip parodyta kitame kodų sąraše.

Person.java (pateiktas aiškus lygus metodas)

pakuotė dustin.pavyzdžiai; public class Asmuo {private final Stygos pavardė; privati ​​finalinė eilutė firstName; viešasis asmuo (final String newLastName, final String newFirstName) {this.lastName = newLastName; this.firstName = newFirstName; } @Paisyti viešosios loginės loginės vertės lygu (Object obj) {if (obj == null) {return false; } jei (tai == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {grąžinti klaidingą; } galutinis Asmuo kitas = (Asmuo) obj; if (this.pavarde == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {grąžinti klaidingą; } if (this.firstName == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {grąžinti klaidingą; } return true; } @Paisyti viešosios eilutės toString () {grąžinti šį.vardas + "" + tai.pavardė; }} 

Kada tai Asmuo su lygus (objektas) naudojami aiškiai apibrėžti, išvestis yra tokia, kaip parodyta kitame ekrano momentiniame vaizde.

Pirmasis pastebėjimas yra tas, kad dabar lygu ragina Asmuo atvejų tikrai grįžta tiesa kai objektas yra lygus, atsižvelgiant į tai, kad visi atributai yra vienodi, o ne tikrinant griežtą nuorodų lygybę. Tai rodo, kad paprotys lygu įgyvendinimas Asmuo atliko savo darbą. Antrasis pastebėjimas yra tas, kad lygu metodas neturėjo jokios įtakos galimybei pridėti ir pašalinti iš pažiūros tą patį objektą „HashSet“.

Aiškus lygu ir hashCode Metodai

Atėjo laikas pridėti aiškų žodį hashCode () metodas Asmuo klasė. Iš tiesų, tai iš tikrųjų turėjo būti padaryta, kai lygu metodas buvo įgyvendintas. Priežastis nurodoma dokumentuose Object.equals (objektas) metodas:

Atkreipkite dėmesį, kad paprastai reikia nepaisyti hashCode metodo, kai šis metodas yra nepaisomas, kad būtų išlaikyta bendra hashCode metodo sutartis, kurioje teigiama, kad vienodi objektai turi turėti vienodus maišos kodus.

Čia yra Asmuo su aiškiai įgyvendinta hashCode metodas, pagrįstas tais pačiais atributais Asmuo kaip lygu metodas.

Person.java (aiškus lygus ir hashCode diegimas)

pakuotė dustin.pavyzdžiai; public class Asmuo {private final Stygos pavardė; privati ​​finalinė eilutė firstName; viešasis asmuo (final String newLastName, final String newFirstName) {this.lastName = newLastName; this.firstName = newFirstName; } @Paisyti viešąjį int hashCode () {return lastName.hashCode () + firstName.hashCode (); } @Paisyti viešosios loginės loginės vertės lygu (Object obj) {if (obj == null) {return false; } jei (tai == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {grąžinti klaidingą; } galutinis Asmuo kitas = (Asmuo) obj; if (this.pavarde == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {grąžinti klaidingą; } if (this.firstName == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {grąžinti klaidingą; } return true; } @Paisyti viešosios eilutės toString () {grąžinti šį.vardas + "" + tai.pavardė; }} 

Rezultatas paleidus naują Asmuo klasė su hashCode ir lygu metodai rodomi toliau.

Nenuostabu, kad maišos kodai, grąžinti objektams, turintiems tas pačias atributų reikšmes, dabar yra vienodi, tačiau įdomesnis pastebėjimas yra tas, kad mes galime pridėti tik du iš keturių atvejų „HashSet“ dabar. Taip yra todėl, kad trečiuoju ir ketvirtuoju bandymu pridėti bandoma laikyti objektą, kuris jau buvo pridėtas prie rinkinio. Kadangi jų buvo tik du, galima rasti ir pašalinti tik du.

Problema su keičiamaisiais hashCode atributais

Ketvirtąjį ir paskutinį šio įrašo pavyzdį apžvelgiu, kas nutinka, kai hashCode įgyvendinimas pagrįstas atributu, kuris keičiasi. Šiame pavyzdyje a setFirstName metodas pridedamas prie Asmuo ir galutinis modifikatorius pašalinamas iš jo Pirmas vardas atributas. Be to, pagrindinėje „HashAndEquals“ klasėje komentaras turi būti pašalintas iš eilutės, kurioje naudojamas šis naujas rinkinio metodas. Nauja versija Asmuo rodomas toliau.

pakuotė dustin.pavyzdžiai; public class Asmuo {private final Stygos pavardė; privati ​​eilutė firstName; viešasis asmuo (final String newLastName, final String newFirstName) {this.lastName = newLastName; this.firstName = newFirstName; } @Paisyti viešąjį int hashCode () {return lastName.hashCode () + firstName.hashCode (); } public void setFirstName (galutinė eilutė newFirstName) {this.firstName = newFirstName; } @Paisyti viešosios loginės loginės vertės lygu (Object obj) {if (obj == null) {return false; } jei (tai == obj) {return true; } if (this.getClass ()! = obj.getClass ()) {grąžinti klaidingą; } galutinis Asmuo kitas = (Asmuo) obj; if (this.pavarde == null? other.lastName! = null:! this.lastName.equals (other.lastName)) {grąžinti klaidingą; } if (this.firstName == null? other.firstName! = null:! this.firstName.equals (other.firstName)) {grąžinti klaidingą; } return true; } @Paisyti viešosios eilutės toString () {grąžinti šį.vardas + "" + tai.pavardė; }} 

Rezultatas, gautas vykdant šį pavyzdį, rodomas toliau.

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