CodeGym /Curs Java /Modulul 2: Java Core /Metodele equals & hashCode: de ce și unde să le folosiți ...

Metodele equals & hashCode: de ce și unde să le folosiți și cum funcționează

Modulul 2: Java Core
Nivel , Lecţie
Disponibil

„Acum vă voi spune despre câteva metode care sunt la fel de utile:  equals(Object o) & hashCode() ”.

„Probabil că v-ați amintit deja că, în Java, atunci când comparați variabile de referință, obiectele în sine nu sunt comparate, ci mai degrabă referințele la obiecte.”

Cod Explicaţie
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i nu este egal cu j
Variabilele indică obiecte diferite.
Chiar dacă obiectele conțin aceleași date.
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
i este egal cu j. Variabilele conțin o referință la același obiect.

— Da, îmi amintesc asta.

Cei  egali .

„Metoda equals este soluția standard aici. Scopul metodei equals este de a determina dacă obiectele sunt identice în interior, comparând ceea ce este stocat în interiorul lor.”

— Și cum face asta?

„Totul este foarte asemănător cu metoda toString()”.

Clasa Object are propria sa implementare a metodei equals, care pur și simplu compară referințele:

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

— Grozav... înapoi la asta, nu-i așa?

"Ține-ți bărbia sus! De fapt, este foarte complicat."

"Această metodă a fost creată pentru a permite dezvoltatorilor să o suprascrie în propriile lor clase. La urma urmei, doar dezvoltatorul unei clase știe ce date sunt relevante și ce nu sunt atunci când compară."

„Poți să dai un exemplu?”

„Sigur. Să presupunem că avem o clasă care reprezintă fracții matematice. Ar arăta astfel:”

Exemplu:
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;
}
}
Exemplu de apel de metodă:
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
Apelul metodei va returna true.
Fracția 2/3 este egală cu fracția 4/6

„Acum, hai să analizăm acest exemplu”.

„Am înlocuit metoda equals , astfel încât obiectele Fraction vor avea propria lor implementare.

„Există mai multe verificări în metodă:”

" 1)  Dacă obiectul trecut pentru comparare este nul , atunci obiectele nu sunt egale. Dacă puteți apela metoda equals pe un obiect, atunci cu siguranță nu este nul ."

" 2)  O comparație de clasă. Dacă obiectele sunt instanțe ale unor clase diferite, atunci nu vom încerca să le comparăm. În schimb, vom folosi imediat return false pentru a indica că acestea sunt obiecte diferite."

" 3)  Toată lumea își amintește din clasa a doua că 2/3 este egal cu 4/6. Dar cum verifici asta?"

2/3 == 4/6
Înmulțim ambele părți cu ambii divizori (6 și 3) și obținem:
6 * 2 == 4 * 3
12 == 12
Regula generala:
Dacă
a / b == c / d
Atunci
a * d == c * b

„În consecință, în a treia parte a metodei equals , aruncăm obiectul transmis într-o Fracție și comparăm fracțiile.”

"Am înțeles. Dacă pur și simplu comparăm numărătorul cu numărătorul și numitorul cu numitorul, atunci 2/3 nu este egal cu 4/6."

„Acum înțeleg ce ai vrut să spui când ai spus că doar dezvoltatorul unei clase știe să o compare corect.”

„Da, dar asta este doar jumătate din poveste.  Există o altă metodă: hashCode().

„Totul despre metoda equals are sens acum, dar de ce avem nevoie de  hashCode ()?

„ Metoda hashCode este necesară pentru comparații rapide.”

" Metoda equals are un dezavantaj major: funcționează prea lent. Să presupunem că aveți un set de milioane de elemente și trebuie să verificați dacă conține un anumit obiect. Cum faceți asta?"

„Aș putea parcurge toate elementele folosind o buclă și pot compara obiectul cu fiecare obiect din set. Până găsesc o potrivire.”

"Și dacă nu este acolo? Am face un milion de comparații doar pentru a afla că obiectul nu este acolo? Nu vi se pare mult?"

"Da, chiar și eu recunosc că sunt prea multe comparații. Există o altă cale?"

„Da, puteți folosi hashCode () pentru asta.

Metoda hashCode () returnează un număr specific pentru fiecare obiect. Dezvoltatorul unei clase decide ce număr este returnat, la fel cum face el sau ea pentru metoda equals.

„Să ne uităm la un exemplu:”

„Imaginați-vă că aveți un milion de numere din 10 cifre. Apoi, puteți face ca codul hash al fiecărui număr să fie restul după împărțirea numărului la 100.”

Iată un exemplu:

Număr Codul nostru hash
1234567890 90
9876554321 21
9876554221 21
9886554121 21

"Da, asta are sens. Și ce facem cu acest cod hash?"

„În loc să comparăm numerele, le comparăm codurile hash . Este mai rapid așa.”

„Și numim egali numai dacă codurile lor hash sunt egale.”

"Da, asta e mai rapid. Dar încă mai trebuie să facem un milion de comparații. Compară doar numere mai mici și încă trebuie să numim egali pentru orice numere cu coduri hash care se potrivesc."

„Nu, poți scăpa cu un număr mult mai mic de comparații”.

„Imaginați-vă că Setul nostru stochează numere grupate sau sortate după hashCode (sortarea lor în acest fel înseamnă, în esență, gruparea lor, deoarece numerele cu același hashCode vor fi unul lângă celălalt). Apoi puteți elimina foarte rapid și ușor grupurile irelevante. Este suficient pentru a verifica o dată pe grup pentru a vedea dacă codul hash se potrivește cu codul hash al obiectului."

„Imaginați-vă că ești un student care caută un prieten pe care îl poți recunoaște din vedere și despre care știm că locuiește în Căminul 17. Apoi mergi la fiecare cămin de la universitate și întrebi: „Este Căminul 17?” Dacă nu este, atunci îi ignori pe toți cei din cămin și treci la următorul. Dacă răspunsul este „da”, atunci începi să treci pe lângă fiecare dintre camere, căutându-ți prietenul.”

„În acest exemplu, numărul căminului (17) este hashCode.”

„Un dezvoltator care implementează o funcție hashCode trebuie să cunoască următoarele:”

A)  două obiecte diferite pot avea același cod hash  (persoane diferite pot locui în același cămin)

B)  obiectele care sunt aceleași  ( conform metodei equalstrebuie să aibă același hashCode. .

C)  codurile hash trebuie alese astfel încât să nu existe o mulțime de obiecte diferite cu același hashCode.  Dacă există, atunci potențialele avantaje ale codurilor hash se pierd (ajungi la Căminul 17 și descoperi că jumătate din universitate locuiește acolo. Păcat!).

„Și acum cel mai important lucru. Dacă suprascriiți metoda equals , trebuie neapărat să înlocuiți metoda hashCode () și să respectați cele trei reguli descrise mai sus.

„Motivul este următorul: în Java, obiectele dintr-o colecție sunt întotdeauna comparate/preluate folosind hashCode() înainte de a fi comparate/preluate folosind equals.  Și dacă obiecte identice au hashCodes diferite, atunci obiectele vor fi considerate diferite și metoda equals. nici nu va fi sunat.

„În exemplul nostru Fraction, dacă am făcut hashCode egal cu numărătorul, fracțiile 2/3 și 4/6 ar avea hashCodes diferite. Fracțiile sunt aceleași, iar metoda equals spune că sunt aceleași, dar hashCodes-ul lor spune sunt diferite. Și dacă comparăm folosind hashCode înainte de a compara folosind equals, atunci ajungem la concluzia că obiectele sunt diferite și nici măcar nu ajungem la metoda equals."

Iată un exemplu:

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
Dacă metoda hashCode()  returnează numărătorul fracțiilor, rezultatul va fi  fals .
Iar obiectul «noua Fracție(4,6) » nu va fi găsit în colecție.

„Deci, care este modalitatea corectă de implementare a codului hash pentru fracții?”

„Aici trebuie să rețineți că fracțiile echivalente trebuie să aibă același cod hash.”

Versiunea 1 : codul hash este egal cu rezultatul divizării întregului.”

„Pentru 7/5 și 6/5, acesta ar fi 1”.

„Pentru 4/5 și 3/5, acesta ar fi 0”.

„Dar această opțiune este prost potrivită pentru compararea fracțiilor care sunt în mod deliberat mai mici de 1. HashCode (rezultatul diviziunii întregi) va fi întotdeauna 0.”

" Versiunea 2 : hashCode este egal cu rezultatul împărțirii întregi a numitorului la numărător."

„Această opțiune este potrivită pentru cazurile în care fracția este mai mică de 1. Dacă fracția este mai mică de 1, atunci inversul său este mai mare decât 1. Și dacă inversăm toate fracțiile, atunci comparațiile nu sunt în niciun fel afectate.”

„Versiunea noastră finală combină ambele soluții:”

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

Să-l testăm folosind 2/3 și 4/6. Ar trebui să aibă coduri hash identice:

Fracția 2/3 Fracția 4/6
numărătorul numitor 2/3 == 0 4/6 == 0
numitor / numărător 3/2 == 1 6/4 == 1
numărător / numitor
+
numitor / numărător
0 + 1 == 1 0 + 1 == 1

"Asta este tot pentru acum."

— Mulțumesc, Ellie. A fost foarte interesant.

Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION