JavaRush /Курси /Java Syntax Zero /Порівняння об'єктів

Порівняння об'єктів

Java Syntax Zero
Рівень 11 , Лекція 5
Відкрита

1. Порівняння об'єктів у Java

Об'єкти у Java можна порівнювати як за посиланням, так і за значенням.

Порівняння посилань

Якщо дві змінні вказують на один і той же об'єкт у пам'яті, то посилання, які зберігаються у цих змінних, рівні. Якщо порівняти такі змінні за допомогою оператора рівності ==, ви отримаєте true, що логічно. Тут все просто.

Код Вивід на екран
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

Порівняння за значенням

Однак часто можна зустріти ситуацію, коли дві змінні посилаються на два різних, але ідентичних об'єкти. Наприклад, два рядки, які містять однаковий текст, але знаходяться у різних об'єктах.

Для визначення ідентичності різних об'єктів потрібно використовувати метод equals() Приклад:

Код Вивід на екран
String a = new String("Привіт");
String b = new String("Привіт");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

Метод equals є не тільки у класу String — він є у взагалі всіх класів.

Навіть у тих, які ви тільки будете писати, і ось чому.



2. Клас Object

Усі класи в Java вважаються успадкованими від класу Object. Це розробники Java так придумали.

А якщо якийсь клас успадкований від класу Object, у цьому класі-нащадку з'являються всі методи класу Object. Це і є головний ефект успадкування.

Іншими словами, у кожного класу, навіть якщо це не написано в його коді, є всі методи, які є у класу Object.

А серед таких методів є методи, які мають відношення до порівняння об'єктів. Це метод equals() і метод hashCode().

Код Як буде насправді:
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 адрес_объекта_в_памяти; //це дефолтна реалізація, але може бути і інша }
}

У прикладі вище ми створили простий клас Person із параметрами name і age, без жодного методу. Однак, оскільки всі класи вважаються успадкованими від класу Object, у класу Person приховано з'явилися два методи:

Метод Опис
boolean equals(Object obj)
Порівнює поточний об'єкт і переданий об'єкт
int hashCode()
Повертає hash-code поточного об'єкта

Виходить, що методи equals є у абсолютно всіх об'єктів і можна порівнювати між собою об'єкти різних типів, і це все буде чудово компілюватися та працювати.

Код Вивід на екран
Integer a = 5;
String s = "Привіт";
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()

Успадкований від класу Object метод equals() містить найпростіший алгоритм порівняння поточного і переданого об'єктів — він просто порівнює їхні посилання.

Той самий ефект ви отримаєте, якщо просто порівняєте змінні класу Person замість виклику методу equals(). Приклад:

Код Вивід на екран
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

Метод equals просто порівнює у себе всередині посилання a і b.

Однак у класу String порівняння працює інакше. Чому?

Тому що розробники класу String написали власну реалізацію методу equals().

Реалізація методу equals()

Давайте і ми напишемо свою реалізацію методу equals в класі Person. Розберемо 4 основних випадки.

Важливо:
Незалежно від того, для якого класу перевизначати метод equals, він завжди приймає параметр типу Object

Сценарій 1: у метод equals передали той самий об'єкт, у якого викликали метод equals. Якщо посилання у поточного і переданого об'єктів рівні, потрібно повернути true. Об'єкт збігається сам із собою.

У коді це буде виглядати так:

Код Опис
public boolean equals(Object obj)
{    if (this == obj)
    return true;

   інший код методу equals
}


Порівнюємо посилання

Сценарій 2: у метод equals передали посилання null — порівнювати нема з чим. Об'єкт, у якого викликали метод equals, точно не null, отже, у цьому випадку потрібно повернути false.

У коді це буде виглядати так:

Код Опис
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   інший код методу equals
}


Порівнюємо посилання


Переданий об'єкт — null?

Сценарій 3: у метод equals передали посилання на об'єкт взагалі не класу Person. Чи рівний об'єкт класу Person об'єкту класу не-Person? Тут вже вирішує сам розробник класу Person — як хоче, так і зробить.

Але зазвичай все ж таки об'єкти вважаються рівними, якщо це об'єкти одного класу. Тому якщо в наш метод equals передали об'єкт не класу Person, ми будемо завжди повертати false. А як перевірити, якого типу об'єкт? Правильно: за допомогою оператора instanceof.

Ось як буде виглядати наш новий код:

Код Опис
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

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

   інший код методу equals
}


Порівнюємо посилання


Переданий об'єкт — null?


Якщо переданий об'єкт не типу Person

4. Порівняння двох об'єктів Person

Що ми отримали в результаті? Якщо ми дійшли до кінця методу, значить, у нас об'єкт типу Person і посилання не null. Тоді перетворюємо його до типу Person і будемо порівнювати внутрішні частини обох об'єктів. Це і є наш сценарій номер 4.

Код Опис
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;

   решта коду методу equals
}


Порівнюємо посилання


Переданий об'єкт — null?


Якщо переданий об'єкт не типу Person


Операція приведення типу

А як порівнювати два об'єкти Person? Вони рівні, якщо у них рівні імена (name) і вік (age). Остаточний код буде виглядати так:

Код Опис
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;
}


Порівнюємо посилання


Переданий об'єкт — null?


Якщо переданий об'єкт не типу Person


Операція приведення типу

Але і це ще не все.

По-перше, поле name має тип String, а значить, поля name потрібно порівнювати за допомогою виклику методу equals.

this.name.equals(person.name)

По-друге, поле name цілком може бути рівним null: тоді викликати метод equals у нього не можна. Потрібна додаткова перевірка на null:

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

Однак якщо name дорівнює null в обох об'єктах Person, імена все-таки рівні.

Код четвертого сценарію може виглядати, наприклад, так:

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


Якщо вік не рівний,
одразу return false

Якщо this.name дорівнює null, немає сенсу порівнювати через equals. Тут або друге поле name дорівнює null, або ні.

Порівнюємо два поля name через equals.


5. Метод hashCode()

Окрім методу equals, який виконує детальне порівняння всіх полів обох об'єктів, є ще один метод, який може використовуватися для неточного, але дуже швидкого порівняння — hashCode().

Уявіть, що ви сортуєте в алфавітному порядку список із тисяч слів, і вам потрібно постійно попарно порівнювати слова. А слова довгі, і букв у них багато. У загальному таке порівняння буде йти дуже довго.

Однак його можна пришвидшити. Припустимо, слова починаються на різні букви: одразу ж зрозуміло, що вони різні. От якщо вони починаються на однакові букви, тоді гарантій немає: у подальшому слова можуть виявитися або рівними, або різними.

Метод hashCode() працює за схожим принципом. Якщо його викликати у об'єкта, то він поверне яке-небудь число — аналог першої букви в слові. Це число має такі властивості:

  • У однакових об'єктів завжди однакові hash-code
  • У різних об'єктів можуть бути однакові hash-code, а можуть бути різні
  • Якщо у об'єктів різні hash-code, об'єкти точно різні

Для кращого розуміння перепишемо ці властивості стосовно слів:

  • У однакових слів завжди однакові перші букви
  • У різних слів можуть бути однакові перші букви, а можуть бути і різні
  • Якщо у слів різні перші букви, слова точно різні

Остання властивість і використовується для пришвидшеного порівняння об'єктів:

Спочатку у двох об'єктів обчислюються hash-code. Якщо ці hash-code різні, то об'єкти точно різні, і порівнювати їх далі не потрібно.

А от якщо hash-code однакові, доведеться все ж порівнювати об'єкти за допомогою equals.



6. Контракти в коді

Описана вище поведінка має бути реалізована у всіх класах у Java. Перевірити правильність порівняння об'єктів на рівні компіляції ніяк не можна.

Усі Java-програмісти домовилися, що якщо вони пишуть свою реалізацію методу equals() замість стандартної (з класу Object), вони також повинні написати свою реалізацію методу hashCode(), щоб озвучені вище правила зберігалися.

Така домовленість називається контрактом.

Якщо ви додаєте у свій клас лише реалізацію одного методу equals() або лише hashCode(), ви грубо порушуєте контракт (порушуєте домовленість). Так робити не можна.

Якщо інші програмісти будуть використовувати ваш код, він може працювати неправильно. Більше того, ви теж будете використовувати код, який працює на основі вищеозвучених контрактів.

Важливо!

Усі колекції в Java при пошуку елемента всередині колекції спочатку порівнюють hash-code об'єктів, а тільки потім викликають для порівняння метод equals.

Тому якщо ви напишете свій клас, а в ньому нову функцію equals, але не напишете метод hashCode() або реалізуєте його з помилками, колекції можуть неправильно працювати з вашими об'єктами.

Наприклад, ви додали об'єкт у список, потім шукаєте його за допомогою методу contains(), а колекція ваш об'єкт не знаходить.

Коментарі (37)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
IronMan57 Рівень 28
10 грудня 2024
До цієї теми дуже корисні додаткові публікації від javarush, зокрема ось ця - Методы equals & hashCode: практика использования
Dmytrii Рівень 28
24 червня 2024
зачем в примерах давать код, который вообще не работает?
Гаркін Рівень 14
15 квітня 2024
А що, лише для мене instanceof це щось новеньке? Ну ок, пішов гуглити. Як же мене вибішують фрази на зразок

Правильно: за допомогою оператора instanceof.
Звідкіля я знаю що вірно і так треба діяти?! Де ми це розбирали?!
Гаркін Рівень 14
15 квітня 2024
Прочитав https://javarush.com/groups/posts/2018-kak-rabotaet-operator-instanceof і здається, що зрозумів. Але наскільки зрозумів покажуть завдання.
Гаркін Рівень 14
24 квітня 2024
Але і гарні новини. Наступна лекція буде ліками на змучену душу бажаючих навчитись програмуванню на Java.
25 лютого 2024
Для 3 задачи великолепно подошел первый комментарий от Оли (в комментах к самой задаче).
Гаркін Рівень 14
24 квітня 2024
Посилання на обговорення - https://javarush.com/tasks/ua.javarush.task.pro.task10.task1011#discussion А якщо потрібні пояснення про super() то сюди https://javarush.com/groups/posts/1927-konstruktorih-bazovihkh-klassov-- Та й щодо порядку ініціалізації змінних та роботи конструктора з цим посиланням варто ознайомитись.
Illia_Losiei Рівень 16 Expert
29 листопада 2023
Питання до сценарію 4 "Порівняння двох об'єктів Person": Навіщо нам виконувати операцію перетворення типу

Person person = (Person) obj;
якщо в сценарії 3 ми вже з'ясували, що у метод equals передано посилання на об'єкт, який належить до класу Person?
Arthur Рівень 70
14 грудня 2023
Тому що instanceof це лише перевірка, чи можливо привести тип obj до типу Person.

Person person = (Person) obj;
А ця дія вже безпосередньо приводить до типу Person. Не можна виконувати приведення типів, не перевірвши можливість такої дії, інакше у вас виникне помилка під час роботи.
Ульяна Рівень 60 Expert
8 листопада 2023
У лекції наведено приклад коду:

Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;
який, згідно лекції, повертає в консоль true. Але ж оскільки у класу Object метод equals порівнює ссилки на об'єкти (==) , а в цьому коді створюється 2 об'єкти, то змінні a та b мають різні посилання, а отже вивод у консоль повинен бути false. Чи я щось неправильно зрозуміла?
Dmytrii Рівень 28
24 червня 2024
данный код вообще не компилируется
IronMan57 Рівень 28
10 грудня 2024
Насправді змінні a та b посилаются на екземпляри класу Integer, так як створюються екземпляри саме цього класу. А в класі Integer метод equals перевизначено.
IronMan57 Рівень 28
10 грудня 2024
Dmytro, в мене код компілюється і працює, але при компіляції видається таке повідомлення - "Integer(int) in Integer has been deprecated and marked for removal" (був визнаний застарілим та позначено для видалення). В мене встановлена 17-а версія Java. Можливо, у вас версія новіша, і цей функціонал вже було видалено. З цього прикладу можна побачити, що матеріал в актуальному стані не підтримується вже багато років.
Pavlo Kezin Рівень 23
24 серпня 2023
Валідатор не пропускав задачу через використання a.equals(b) замість Objects.equals(a, b).... щось я не памятаю, що ми це проходили... взагалі, лекція розроблена жахливо. Стала хоч щось зрозуміло тільки після інших статтей на цьому ж сайті.
Viacheslav B. Рівень 1
20 лютого 2024
так десь на початку , мабуть у лекції про корисну літературу вони говорили що треба користуватися додаткової літературою, тобто рішення деяких задач треба шукати у іншому місці.
25 лютого 2024
Когда я пытался учиться по программе ЕРАМ, там тоже смеялись как дети, типа интернет большой. Тут я заплатил деньги за то, чтобы мне дали базовую информацию и закрепили задачами. А не снова тот стиль, мол интернет большой. Вопрос риторический, а зачем такое обучение если интернет большой? Наверное, чтобы сэкономить время и ресурсы??? Научив читать, я дальше как-то сам..... А не на тебе азбуку и вперед...
Antek_Ukraine Рівень 14 Expert
9 липня 2023
А мені задача з оверрайдом hashCode () спочатку не заходила, аж півгодини думав. А потім як зайшла) вирішується однією строкою. подумайте, як за допомогою суми двох чисел (підказка) перевірити, чи РІВНІ строка та інт двох окремо взятих елементів.
mr.Coder Рівень 35
20 червня 2023
Objects.hash(model, year); Хіба цей метод десь упоминався раніше в лекціях?
Max Рівень 20
7 липня 2023
Про те як реалізувати hash гуглив інфу в інтернеті, і там було не так, цей варіант побачив лише в обговоренні задачі і тут
Олег Рівень 111 Expert
17 квітня 2023
Дивлюсь на другу задачу і думаю ... вірніше не думаю - просто уважно дивлюсь ... і вона на мене дивиться... і між нами сірий шум.