1. Compararea obiectelor în Java

În Java, obiectele pot fi comparate atât prin referință, cât și prin valoare.

Compararea referințelor

Dacă două variabile indică același obiect din memorie, atunci referințele stocate în aceste variabile sunt egale. Dacă comparați aceste variabile folosind operatorul de egalitate ( ==), obțineți adevărat și rezultatul are sens. Totul este simplu aici.

Cod Ieșire de consolă
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

Compararea după valoare

Dar puteți întâlni adesea situații în care două variabile se referă la două obiecte distincte care sunt identice. De exemplu, două șiruri diferite de obiecte care conțin același text.

Pentru a determina dacă diferite obiecte sunt identice, utilizați equals()metoda. De exemplu:

Cod Ieșire de consolă
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

Metoda equalsnu se limitează la Stringclasă. Fiecare clasă o are.

Chiar și cursuri pe care le scrii singur și iată de ce.



2. Objectclasa

Toate clasele din Java moștenesc Objectclasa. Creatorii lui Java au venit cu această abordare.

Și dacă o clasă moștenește Objectclasa, atunci câștigă toate metodele clasei Object. Și aceasta este o consecință majoră a moștenirii.

Cu alte cuvinte, fiecare clasă are metodele clasei Object, chiar dacă codul lor nu le menționează.

Aceste metode moștenite includ metode legate de compararea obiectelor. Acestea sunt metodele equals()și hashCode().

Cod În realitate, iată ce vom avea:
class Person
{
   String name;
   int age;
}
class Person extends Object
{
   String name;
   int age;

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

   public int hashCode()
   {
      return address_of_object_in_memory; // This is the default implementation, but there may be a different implementation
   }
}

În exemplul de mai sus, am creat o Personclasă simplă cu parametri de nume și vârstă, dar nu o singură metodă. Dar, deoarece toate clasele moștenesc Objectclasa, Personclasa are automat două metode:

Metodă Descriere
boolean equals(Object obj)
Compară obiectul curent și obiectul trecut
int hashCode()
Returnează codul hash al obiectului curent

Se pare că absolut fiecare obiect are equalsmetoda, iar obiectele de diferite tipuri pot fi comparate între ele. Un astfel de cod se va compila și va funcționa perfect.

Cod Ieșire de consolă
Integer a = 5;
String s = "Hello";
System.out.println(a.equals(s));
System.out.println(s.equals(a));


false
false
Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;


true

3. equals()metoda

Metoda equals(), moștenită de la Objectclasă, implementează cel mai simplu algoritm pentru compararea obiectului curent cu obiectele trecute: doar compară referințele la obiecte.

Obțineți același rezultat dacă doar comparați Personvariabile în loc să apelați equals()metoda. Exemplu:

Cod Ieșire de consolă
Person a = new Person();
a.name = "Steve";

Person b = new Person();
b.name = "Steve";

System.out.println(a == b);
System.out.println(a.equals(b));






false
false

Când equalsmetoda este apelată pe a, ea pur și simplu compară referința stocată în avariabilă cu referința stocată în bvariabilă.

Cu toate acestea, comparația funcționează diferit pentru Stringclasă. De ce?

Pentru că cei care au creat Stringclasa și-au scris propria implementare a equals()metodei.

Implementarea equals()metodei

Acum să scriem propria noastră implementare a metodei equals în Personclasă. Vom lua în considerare 4 cazuri principale.

Important:
Indiferent de clasa care suprascrie equalsmetoda, aceasta ia întotdeauna un Objectobiect ca argument

Scenariul 1 : același obiect pe care equalseste apelată metoda este de asemenea transmis metodei equals. Dacă referințele obiectului curent și ale obiectului trecut sunt egale, metoda trebuie să returneze true. Un obiect este egal cu el însuși.

În cod va arăta astfel:

Cod Descriere
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

   // The rest of the code of the equals method
}


Comparați referințele

Scenariul 2 : nulleste trecut la equalsmetodă — nu avem cu ce să comparăm. Obiectul pe care equalseste apelată metoda nu este cu siguranță nul, așa că trebuie să revenim falseîn acest caz.

În cod va arăta astfel:

Cod Descriere
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   // The rest of the code of the equals method
}


Comparare referințe


Este obiectul trecut null?

Scenariul 3 : o referință la un obiect care nu este un Personeste transmisă metodei equals. Este Personobiectul egal cu non- Personobiectul? Aceasta este o întrebare pentru care dezvoltatorul clasei Persontrebuie să decidă cum dorește.

Dar, de obicei, obiectele trebuie să fie din aceeași clasă pentru a fi considerate egale. Prin urmare, dacă altceva decât un obiect al Personclasei este transmis metodei noastre equals, atunci vom reveni întotdeauna false. Cum poți verifica tipul unui obiect? Așa este, folosind instanceofoperatorul.

Iată cum arată noul nostru cod:

Cod Descriere
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   // The rest of the code of the equals method
}


Comparare referințe


Este obiectul trecut null?


Dacă obiectul trecut nu este aPerson

4. Compararea a două Personobiecte

Cu ce ​​am ajuns? Dacă am ajuns la sfârșitul metodei, atunci avem o Personreferință la obiect care nu este null. Deci îl convertim în a Personși comparăm datele interne relevante ale ambelor obiecte. Și acesta este al patrulea scenariu al nostru .

Cod Descriere
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   // The rest of the code of the equals method
}


Comparare referințe


Este obiectul trecut null?


Dacă obiectul transmis nu este un Person


Typecasting

Și cum compar două Personobiecte? Sunt egali dacă au același nume ( name) și vârstă ( age). Codul final va arăta astfel:

Cod Descriere
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   return this.name == person.name && this.age == person.age;
}


Comparare referințe


Este obiectul trecut null?


Dacă obiectul transmis nu este un Person


Typecasting

Dar asta nu este tot.

În primul rând, câmpul de nume este un String, așa că trebuie să comparați câmpul de nume apelând metoda equals.

this.name.equals(person.name)

În al doilea rând, namecâmpul poate fi null: în acest caz, nu îl puteți apela equals. Aveți nevoie de o verificare suplimentară pentru null:

this.name != null && this.name.equals(person.name)

Acestea fiind spuse, dacă câmpul nume este nullîn ambele Personobiecte, atunci numele sunt în continuare egale.

Codul pentru al patrulea scenariu ar putea arăta astfel:

Person person = (Person) obj;

if (this.age != person.age)
   return false;

if (this.name == null)
   return person.name == null;

return this.name.equals(person.name);


Dacă vârstele nu sunt egale,
imediat return false

Dacă this.nameeste egal cu null, nu are rost să comparați folosind equalsmetoda. Aici, fie al doilea namecâmp este egal cu null, fie nu este.

Comparați cele două câmpuri de nume folosind equalsmetoda.


5. hashCode()metoda

Pe lângă equalsmetodă, care are scopul de a efectua o comparație detaliată a tuturor câmpurilor ambelor obiecte, există o altă metodă care poate fi folosită pentru o comparație imprecisă, dar foarte rapidă: hashCode().

Imaginați-vă că sortați alfabetic o listă de mii de cuvinte și că trebuie să comparați în mod repetat perechi de cuvinte. Și cuvintele sunt lungi, constând din o mulțime de litere. În general, o astfel de comparație ar dura foarte mult timp.

Dar se poate accelera. Să presupunem că avem cuvinte care încep cu litere diferite - este imediat clar că sunt diferite. Dar dacă încep cu aceleași litere, atunci nu putem spune încă care va fi rezultatul: cuvintele se pot dovedi a fi egale sau diferite.

Metoda hashCode()funcționează folosind un principiu similar. Dacă îl apelați pe un obiect, acesta returnează un număr - analog cu prima literă dintr-un cuvânt. Acest număr are următoarele proprietăți:

  • Obiectele identice au întotdeauna același cod hash
  • Diferite obiecte pot avea același hashcode, sau hashcode-urile lor pot fi diferite
  • Dacă obiectele au coduri hash diferite, atunci obiectele sunt cu siguranță diferite

Pentru a face acest lucru și mai clar, să reformulam aceste proprietăți în termeni de cuvinte:

  • Cuvintele identice au întotdeauna aceleași primele litere.
  • Cuvintele diferite pot avea aceleași primele litere sau primele litere pot fi diferite
  • Dacă cuvintele au primele litere diferite, atunci cuvintele sunt cu siguranță diferite

Ultima proprietate este folosită pentru a accelera compararea obiectelor:

Mai întâi, se calculează codurile hash ale celor două obiecte. Dacă aceste coduri hash sunt diferite, atunci obiectele sunt cu siguranță diferite și nu este nevoie să le comparați în continuare.

Dar dacă codurile hash sunt aceleași, atunci mai trebuie să comparăm obiectele folosind metoda equals.



6. Contracte în cod

Comportamentul descris mai sus trebuie implementat de toate clasele din Java. În timpul compilării, nu există nicio modalitate de a verifica dacă obiectele sunt comparate corect.

Programatorii Java au un acord universal că, dacă își scriu propria implementare a metodei equals() și, prin urmare, suprascriu implementarea standard (în clasă Object), ei trebuie să scrie și propria lor implementare a hashCode()metodei în așa fel încât regulile menționate mai sus să fie multumit.

Acest aranjament se numește contract .

Dacă implementați numai metoda equals()sau numai hashCode()metoda în clasa dvs., atunci vă aflați într-o încălcare gravă a contractului (ați încălcat acordul). Nu face asta.

Dacă alți programatori vă folosesc codul, este posibil să nu funcționeze corect. În plus, veți folosi cod care se bazează pe respectarea contractelor de mai sus.

Important!

Când căutați un element, toate colecțiile Java compară mai întâi codurile hash ale obiectelor și abia apoi efectuează o comparație folosind metoda equals.

Asta înseamnă că dacă dai propriei clase o equalsmetodă, dar nu scrii propria ta hashCode()metodă sau o implementezi incorect, atunci colecțiile pot să nu funcționeze corect cu obiectele tale.

De exemplu, puteți adăuga un obiect la o listă și apoi îl căutați folosind metoda contains(), dar colecția ar putea să nu vă găsească obiectul.