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:
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:
Č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.