CodeGym /Java tanfolyam / /Az equals & hashCode metódusok: miért és hol kell használ...

Az equals & hashCode metódusok: miért és hol kell használni őket, és hogyan működnek

Szint , Lecke
Elérhető

"Most elmondok néhány olyan módszert, amelyek ugyanolyan hasznosak:  equals(Object o) & hashCode() ."

"Valószínűleg már emlékezett rá, hogy a Java-ban a referenciaváltozók összehasonlításakor nem magukat az objektumokat hasonlítják össze, hanem az objektumokra való hivatkozásokat."

Kód Magyarázat
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i nem egyenlő j-vel
A változók különböző objektumokra mutatnak.
Annak ellenére, hogy az objektumok ugyanazokat az adatokat tartalmazzák.
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
i egyenlő j-vel. A változók ugyanarra az objektumra utalnak.

– Igen, erre emlékszem.

Az  egyenlők .

"Az egyenlőség módszer itt a standard megoldás. Az egyenlőség módszer célja annak meghatározása, hogy az objektumok belsőleg azonosak-e a bennük tárolt adatok összehasonlításával."

– És hogyan teszi ezt?

"Ez mind nagyon hasonlít a toString() metódushoz."

Az Object osztálynak megvan a maga egyenlős metódus implementációja, amely egyszerűen összehasonlítja a hivatkozásokat:

public boolean equals(Object obj)
{
return (this == obj);
}

– Remek... térjünk vissza erre, ugye?

"Tartsd fel az állad! Ez valójában nagyon trükkös."

"Ezt a módszert azért hozták létre, hogy a fejlesztők felülírhassák a saját osztályaikban. Végül is csak az osztály fejlesztője tudja, hogy az összehasonlítás során mely adatok relevánsak és melyek nem."

– Tudsz példát mondani?

"Persze. Tegyük fel, hogy van egy osztályunk, amely matematikai törteket reprezentál. Így nézne ki:"

Példa:
class Fraction
{
private int numerator;
private int denominator;
Fraction(int numerator, int denominator)
{
this.numerator  = numerator;
this.denominator = denominator;
}public boolean equals(Object obj)
{
if (obj==null)
return false;

if (obj.getClass() != this.getClass() )
return false;

Fraction other = (Fraction) obj;
return this.numerator* other.denominator == this.denominator * other.numerator;
}
}
Példa metódushívásra:
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
A metódushívás true értéket ad vissza.
A 2/3 tört egyenlő a 4/6 törttel

– Most pedig boncolgassuk ezt a példát.

"Felülírtuk az egyenlőség metódust, így a tört objektumoknak saját megvalósításuk lesz.

"Több ellenőrzés is van a módszerben:"

" 1)  Ha az összehasonlításra átadott objektum null , akkor az objektumok nem egyenlőek. Ha egy objektumon meghívhatja az equals metódust, akkor az biztosan nem null ."

" 2) Osztály-összehasonlítás. Ha az objektumok különböző osztályok példányai, akkor nem próbáljuk meg összehasonlítani őket. Ehelyett azonnal a return false-t  használjuk annak jelzésére, hogy ezek különböző objektumok."

" 3)  Mindenki emlékszik a második osztálytól, hogy a 2/3 egyenlő 4/6-tal. De hogyan lehet ezt ellenőrizni?"

2/3 == 4/6
Mindkét oldalt megszorozzuk mindkét osztóval (6 és 3), és a következőt kapjuk:
6 * 2 == 4 * 3
12 == 12
Általános szabály:
Ha
a / b == c / d
Akkor
a * d == c * b

"Ennek megfelelően az egyenlőségi módszer harmadik részében az átadott objektumot törtté öntjük, és összehasonlítjuk a törteket."

"Értem. Ha egyszerűen összehasonlítjuk a számlálót a számlálóval és a nevezőt a nevezővel, akkor a 2/3 nem egyenlő a 4/6-tal."

"Most már értem, mire gondoltál, amikor azt mondtad, hogy csak egy osztály fejlesztője tudja, hogyan kell helyesen összehasonlítani."

"Igen, de ez csak a történet fele.  Van egy másik módszer: hashCode() .

"Az egyenlőség metódussal kapcsolatban most mindennek van értelme, de miért van szükségünk  a hashCode-ra ()? "

"A hashCode metódus szükséges a gyors összehasonlításhoz."

"Az egyenlőség metódusának van egy nagy hátránya: túl lassan működik. Tegyük fel, hogy több millió elemből álló halmaza van, és ellenőriznie kell, hogy tartalmaz-e egy adott objektumot. Hogyan kell ezt megtenni?"

"Válogathatok az összes elemen egy hurok segítségével, és összehasonlíthatom az objektumot a készlet minden egyes objektumával. Amíg nem találok egyezőt."

"És ha nincs ott? Millió összehasonlítást végeznénk csak azért, hogy megtudjuk, az objektum nincs ott? Nem tűnik soknak?"

– Igen, még én is elismerem, hogy ez túl sok összehasonlítás. Van más mód?

"Igen, használhatja a hashCode- ot () ehhez.

A hashCode () metódus minden objektumhoz egy adott számot ad vissza. Egy osztály fejlesztője dönti el, hogy milyen számot adjon vissza, akárcsak az egyenlő metódus esetében.

– Nézzünk egy példát:

"Képzeld el, hogy van egy millió 10 jegyű számod. Ezután beállíthatod, hogy minden szám hashCode-ja legyen a maradék, miután elosztod a számot 100-zal."

Íme egy példa:

Szám A mi hashCode-unk
1234567890 90
9876554321 21
9876554221 21
9886554121 21

"Igen, ennek van értelme. És mit kezdjünk ezzel a hashCode-dal?"

"A számok összehasonlítása helyett a hashCode- jukat hasonlítjuk össze . Így gyorsabb."

"És csak akkor hívjuk egyenlőnek , ha a hashCode- juk egyenlő."

"Igen, ez gyorsabb. De még mindig milliónyi összehasonlítást kell végeznünk. Csak kisebb számokat hasonlítunk össze, és továbbra is egyenlőt kell hívnunk minden olyan számhoz, amelynek hashCode-ja megegyezik."

– Nem, sokkal kisebb számú összehasonlítással megúszhatod.

"Képzeld el, hogy a készletünk a hashCode szerint csoportosítva vagy rendezve tárolja a számokat (az ilyen rendezés lényegében csoportosítást jelent, hiszen az azonos hashCode-val rendelkező számok egymás mellett lesznek). Ekkor nagyon gyorsan és egyszerűen el tudod dobni a nem releváns csoportokat. Elég. csoportonként egyszer ellenőrizze, hogy a hashCode egyezik-e az objektum hashCode-jával."

"Képzeld el, hogy diák vagy, aki egy barátot keres, akit látásból felismerhetsz, és akiről tudjuk, hogy a 17-es kollégiumban él. Aztán csak bemész az egyetem minden kollégiumába, és megkérdezed: "Ez a 17-es kollégium?" Ha nem, akkor figyelmen kívül hagysz mindenkit a kollégiumban, és továbblépsz a következőre. Ha a válasz "igen", akkor elindulsz az egyes szobák mellett, és a barátodat keresed."

"Ebben a példában a kollégium száma (17) a hashCode."

"A hashCode függvényt megvalósító fejlesztőnek tudnia kell a következőket:"

A)  két különböző objektumnak lehet ugyanaz a hashCode  (különböző emberek élhetnek ugyanabban a kollégiumban)

B)  az azonos objektumoknak  ( az egyenlőség metódus szerintazonos hashCode-kal kell rendelkezniük. .

C)  A hash kódokat úgy kell megválasztani, hogy ne legyen sok különböző objektum ugyanazzal a hashCode-dal.  Ha vannak, akkor a hashcode-ok potenciális előnyei elvesznek (a 17-es kollégiumba jutsz, és azt tapasztalod, hogy az egyetem fele ott lakik. Baj!).

"És most a legfontosabb dolog. Ha felülírja az egyenlő metódust, akkor feltétlenül felül kell írnia a hashCode () metódust, és be kell tartania a fent leírt három szabályt.

"Az ok a következő: Java-ban a gyűjtemény objektumai mindig a hashCode() segítségével kerülnek összehasonlításra/lekérésre, mielőtt összehasonlítják/lekérik őket az egyenlő értékkel.  Ha pedig az azonos objektumoknak különböző hashCode-jaik vannak, akkor az objektumokat különbözőnek tekinti, és az egyenlő metódus nem is hívják.

"A Tört példánkban, ha a hashCode-ot egyenlővé tesszük a számlálóval, akkor a 2/3 és 4/6 törtek különböző hashCode-okkal rendelkeznének. A törtek azonosak, és az egyenlőség metódus szerint ugyanazok, de a hashCode-juk szerint És ha összehasonlítjuk a hashCode használatával, mielőtt összehasonlítanánk az egyenlők használatával, akkor arra a következtetésre jutunk, hogy az objektumok különböznek egymástól, és soha nem jutunk el az egyenlő metódushoz."

Íme egy példa:

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
Ha a hashCode()  metódus visszaadja a törtek számlálóját, az eredmény  false lesz .
És az «új Tört(4,6) » objektum nem lesz megtalálható a gyűjteményben.

"Szóval mi a helyes módja a hashCode megvalósításának a törteknél?"

"Itt emlékeznie kell arra, hogy az egyenértékű törteknek ugyanazzal a hashCode-val kell rendelkezniük."

" 1. verzió : a hashCode megegyezik az egész számok osztásának eredményével."

"7/5 és 6/5 esetében ez 1 lenne."

"4/5 és 3/5 esetében ez 0 lenne."

"De ez az opció nem alkalmas olyan törtek összehasonlítására, amelyek szándékosan kisebbek 1-nél. A hashCode (az egész számok felosztásának eredménye) mindig 0 lesz."

" 2. verzió : a hashCode megegyezik a nevező számlálóval való egész számmal való osztásának eredményével."

"Ez az opció olyan esetekben megfelelő, amikor a tört kisebb, mint 1. Ha a tört kisebb, mint 1, akkor az inverze nagyobb, mint 1. És ha az összes törtet megfordítjuk, akkor az összehasonlításokat ez semmilyen módon nem érinti."

"Végső változatunk mindkét megoldást egyesíti:"

public int hashCode()
{
return numerator/denominator + denominator/numerator;
}

Teszteljük 2/3 és 4/6 segítségével. Azonos hashCode-okkal kell rendelkezniük:

2/3 4/6
számláló / nevező 2/3 == 0 4/6 == 0
nevező / számláló 3/2 == 1 6/4 == 1
számláló / nevező
+
nevező / számláló
0 + 1 == 1 0 + 1 == 1

"Ez minden most."

– Köszönöm, Ellie. Ez nagyon érdekes volt.

Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION