1. Porównywanie obiektów w Javie

Obiekty w Javie można porównywać zarówno według referencji, jak i według wartości.

Porównanie linków

Jeśli dwie zmienne wskazują na ten sam obiekt w pamięci, to odwołania przechowywane w tych zmiennych są równe. Jeśli porównasz takie zmienne za pomocą operatora równości ==, otrzymasz prawdę, co ma sens. Tutaj wszystko jest proste.

Kod Wyjście na wyświetlaczu
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

Porównanie według wartości

Często jednak można spotkać się z sytuacją, w której dwie zmienne odnoszą się do dwóch różnych, ale identycznych obiektów. Na przykład dwa wiersze zawierające ten sam tekst, ale znajdujące się w różnych obiektach.

Aby określić tożsamość różnych obiektów, musisz użyć equals()metody Example:

Kod Wyjście na wyświetlaczu
String a = new String("Cześć");
String b = new String("Cześć");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

equalsNie tylko klasa ma metodę – wszystkie klasy w ogóleString ją mają .

Nawet te, które tylko napiszesz, a oto dlaczego.



2. KlasaObject

Uważa się, że wszystkie klasy w Javie dziedziczą po Object. Tak to wymyślili twórcy Javy.

A jeśli pewna klasa jest dziedziczona z klasy Object, wszystkie metody klasy pojawiają się w tej klasie pochodnej Object. To jest główny efekt dziedziczenia.

Innymi słowy, każda klasa, nawet jeśli nie jest zapisana w swoim kodzie, ma wszystkie metody, które ma klasa Object.

A wśród tych metod są metody związane z porównywaniem obiektów. To jest metoda equals()i metoda hashCode().

Kod Jak to będzie w rzeczywistości:
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 adres_obiektа_в_памяти;         //это дефолтная реализация, но может быть и другая
   }
}

W powyższym przykładzie stworzyliśmy prostą klasę Personz parametrami nazwa i wiek, bez jednej metody. Jednak od wszystkie klasy są uważane za odziedziczone po klasie Object, klasa Personma ukryte dwie metody:

metoda Opis
boolean equals(Object obj)
Porównuje bieżący obiekt i przekazany obiekt
int hashCode()
Zwraca kod skrótu bieżącego obiektu

Okazuje się, że equalsabsolutnie wszystkie obiekty mają metody i można porównywać ze sobą obiekty różnych typów, a to wszystko się skompiluje i będzie działać idealnie.

Kod Wyjście na wyświetlaczu
Integer a = 5;
String s = "Cześć";
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. Metodaequals()

ObjectMetoda odziedziczona po klasie equals()zawiera najprostszy algorytm porównywania obiektów bieżących i przekazywanych - po prostu porównuje ich referencje.

Ten sam efekt uzyskasz, jeśli po prostu porównasz zmienne klasy Personzamiast wywoływać metodę equals().. Przykład:

Kod Wyjście na wyświetlaczu
Person a = new Person();
a.name = "Аня";

Person b = new Person();
b.name = "Аня";

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






false
false

Metoda equalspo prostu porównuje wewnątrz łącza ai b.

Jednak w przypadku klasy Stringporównanie działa inaczej. Dlaczego?

Ponieważ twórcy klasy Stringnapisali własną implementację platformy equals().

Implementacja metodyequals()

Napiszmy własną implementację metody equals w Person. Spójrzmy na 4 główne przypadki.

Ważny:
Niezależnie od tego, dla której klasy metoda jest przesłonięta equals, zawsze przyjmuje parametr typuObject

Scenariusz 1 : equalsten sam obiekt został przekazany do metody, w której metoda została wywołana equals. Jeśli odniesienia bieżącego i przekazanego obiektu są równe, zwróć true. Obiekt pasuje do siebie.

W kodzie będzie to wyglądać tak:

Kod Opis
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

   остальной kod метода equals
}


Porównaj linki

Scenariusz 2 : do metody equalsprzekazano odwołanie null- nie ma z czym porównywać. Obiekt, na który została wywołana metoda, equalsna pewno nie jest null, co oznacza, że ​​w takim przypadku należy zwrócić false.

W kodzie będzie to wyglądać tak:

Kod Opis
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   остальной kod метода equals
}


Porównaj referencje


Obiekt przekazany — null?

Scenariusz 3 : Do metody equalszostało przekazane odwołanie do obiektu bez klasy Person. Czy obiekt klasowy jest równy Personobiektowi nieklasowemu Person? Tutaj sam twórca klasy decyduje Person- zrobi, co zechce.

Ale zwykle obiekty są nadal uważane za równe, jeśli są obiektami tej samej klasy. Dlatego jeśli do naszej metody equals został przekazany obiekt nie należący do klasy Person, zawsze zwrócimy false. Jak sprawdzić, jakiego typu jest obiekt? Zgadza się: używając operatora instanceof.

Oto jak będzie wyglądał nasz nowy kod:

Kod Opis
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

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

   остальной kod метода equals
}


Porównaj referencje


Obiekt przekazany — null?


Jeśli przekazany obiekt nie jest typuPerson

4. Porównanie dwóch obiektówPerson

Na czym skończyliśmy? Jeśli dotarliśmy do końca metody, to mamy obiekt typu, Persona referencją nie jest null. Następnie konwertujemy go na typ Personi porównujemy wnętrza obu obiektów. To jest nasz scenariusz numer 4.

Kod Opis
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;

   остальной kod метода equals
}


Porównaj referencje


Obiekt przekazany — null?


Jeśli przekazany obiekt nie jest Person


typu operacji rzutowania

Jak porównać dwa obiekty Person? Są równe, jeśli mają to samo imię ( name) i wiek ( age). Ostateczny kod będzie wyglądał następująco:

Kod Opis
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;
}


Porównaj referencje


Obiekt przekazany — null?


Jeśli przekazany obiekt nie jest Person


typu operacji rzutowania

Ale to nie wszystko.

Po pierwsze pole name jest typu String, co oznacza, że ​​pola name muszą być porównane przy użyciu wywołania metody equals.

this.name.equals(person.name)

Po drugie, pole namemoże równie dobrze być sobie równe null: wtedy equalsnie można z niego wywołać metody. Dodatkowa weryfikacja jest wymagana dla null:

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

Jeśli jednak nazwa jest taka sama nullw obu obiektach Person, nazwy są nadal równe.

Kod czwartego scenariusza może wyglądać następująco:

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);


Jeśli wiek nie jest równy,
natychmiast . return false

Jeśli this.namerówny null, nie ma sensu porównywać przez equals. Tutaj albo drugie pole namejest równe null, albo nie.

Porównujemy dwa pola nazwa do equals.


5. MetodahashCode()

Oprócz metody equals, która dokonuje szczegółowego porównania wszystkich pól obu obiektów, istnieje jeszcze jedna metoda, która może posłużyć do niedokładnego, ale bardzo szybkiego porównania - hashCode().

Wyobraź sobie, że sortujesz alfabetycznie listę tysięcy słów i musisz stale porównywać słowa w parach. A słowa są długie i jest w nich dużo liter. Ogólnie rzecz biorąc, takie porównanie będzie trwało bardzo długo.

Można to jednak przyspieszyć. Załóżmy, że słowa zaczynają się na różne litery: od razu widać, że są różne. Teraz, jeśli zaczynają się od tych samych liter, nie ma gwarancji: w przyszłości słowa mogą okazać się zarówno równe, jak i różne.

Metoda hashCode()działa w podobny sposób. Jeśli zostanie wywołany na obiekcie, zwróci określoną liczbę - odpowiednik pierwszej litery w słowie. Ta liczba ma następujące właściwości:

  • Te same obiekty mają zawsze ten sam kod skrótu
  • Różne obiekty mogą mieć ten sam kod skrótu lub różne
  • Jeśli obiekty mają różne kody skrótu, obiekty są zdecydowanie różne

Dla lepszego zrozumienia przepisujemy te właściwości w odniesieniu do słów:

  • Te same słowa mają zawsze tę samą pierwszą literę
  • Różne słowa mogą mieć takie same pierwsze litery lub mogą mieć różne.
  • Jeśli słowa mają różne pierwsze litery, słowa są zdecydowanie różne

Ostatnia właściwość służy do przyspieszonego porównywania obiektów:

Najpierw obliczane są kody skrótu dla dwóch obiektów. Jeśli te kody skrótu są różne, to obiekty są zdecydowanie różne i nie ma potrzeby ich dalszego porównywania.

Ale jeśli kody skrótu są takie same, nadal musisz porównywać obiekty za pomocą równości.



6. Umowy w kodzie

Zachowanie opisane powyżej musi być zaimplementowane przez wszystkie klasy w Javie. Nie ma możliwości sprawdzenia poprawności porównywania obiektów na poziomie kompilacji.

Wszyscy programiści Javy zgodzili się, że jeśli zamiast domyślnej (z klasy Object) piszą własną implementację metody equals() , to muszą również napisać własną implementację metody hashCode(), aby powyższe zasady zostały zachowane.

Taka umowa nazywana jest umową .

Jeśli dodasz do swojej klasy implementację tylko jednej metody equals()lub tylko hashCode(), rażąco naruszasz umowę (zrywasz umowę). Nie możesz tego zrobić.

Jeśli inni programiści użyją twojego kodu, może on nie działać poprawnie. Ponadto użyjesz kodu, który działa w oparciu o powyższe umowy.

Ważny!

We wszystkich kolekcjach w Javie, szukając elementu w kolekcji, najpierw porównuje się kod skrótu obiektów, a dopiero potem wywołuje metodę equals.

Dlatego jeśli napiszesz swoją klasę, a w niej nową funkcję equals, ale nie napiszesz metody hashCode()ani nie zaimplementujesz jej z błędami, kolekcje mogą nie działać poprawnie z twoimi obiektami.

Na przykład dodałeś obiekt do listy, a następnie szukasz go za pomocą metody contains(), ale kolekcja nie znajduje twojego obiektu.