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

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

Відкрита

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 — він є взагалі у всіх класів.

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


11
Задача
Java Syntax Zero,  11 рівень5 лекція
Недоступна
Порівняння рядків
У методі main оголошено змінні типу String. У консоль виводиться результат їх порівняння: якщо рядки однакові, виводиться true, інакше — false. Тобі потрібно розкоментувати один рядок так, щоб у консоль виводилося true true Тіло методу main змінювати не можна: дозволено лише розкоментувати один рядо

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.

11
Задача
Java Syntax Zero,  11 рівень5 лекція
Недоступна
Два айфони
У методі main створюються два айфони з однаковими параметрами. У консоль виводиться результат їх порівняння. Розберися, чому зараз результат негативний і зроби так, щоб він був позитивним. Для цього тобі слід перевизначити метод equals(Iphone), який буде враховувати всі параметри. У двох рівних об'є

5. Метод hashCode()

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

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

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

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

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

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

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

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

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

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


11
Задача
Java Syntax Zero,  11 рівень5 лекція
Недоступна
Створюємо свій hashCode
Напиши свою реалізацію hashCode із використанням змінних model і year. Якщо ці поля у двох об'єктів однакові, то має повертатися однаковий hashCode. Якщо правильно реалізувати метод hashCode, результат виведення має бути таким: true true true true false false

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

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

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

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

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

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

Важливо!

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

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

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

Коментарі (37)
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
IronMan57
Рівень 1
10 грудня 2024, 19:35
До цієї теми дуже корисні додаткові публікації від javarush, зокрема ось ця - Методы equals & hashCode: практика использования
Dmytrii
Рівень 32
24 червня 2024, 13:13
зачем в примерах давать код, который вообще не работает?
Гаркін
Рівень 14
15 квітня 2024, 17:58
А що, лише для мене instanceof це щось новеньке? Ну ок, пішов гуглити. Як же мене вибішують фрази на зразок
Правильно: за допомогою оператора instanceof.
Звідкіля я знаю що вірно і так треба діяти?! Де ми це розбирали?!
Гаркін
Рівень 14
15 квітня 2024, 18:06
Прочитав https://javarush.com/groups/posts/2018-kak-rabotaet-operator-instanceof і здається, що зрозумів. Але наскільки зрозумів покажуть завдання.
Гаркін
Рівень 14
24 квітня 2024, 08:10
Але і гарні новини. Наступна лекція буде ліками на змучену душу бажаючих навчитись програмуванню на Java.
25 лютого 2024, 20:28
Для 3 задачи великолепно подошел первый комментарий от Оли (в комментах к самой задаче).
Гаркін
Рівень 14
24 квітня 2024, 11:41
Посилання на обговорення - 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, 07:45
Питання до сценарію 4 "Порівняння двох об'єктів Person": Навіщо нам виконувати операцію перетворення типу
Person person = (Person) obj;
якщо в сценарії 3 ми вже з'ясували, що у метод equals передано посилання на об'єкт, який належить до класу Person?
Arthur
Рівень 70
14 грудня 2023, 17:38
Тому що instanceof це лише перевірка, чи можливо привести тип obj до типу Person.
Person person = (Person) obj;
А ця дія вже безпосередньо приводить до типу Person. Не можна виконувати приведення типів, не перевірвши можливість такої дії, інакше у вас виникне помилка під час роботи.
Ульяна
Рівень 60
Expert
8 листопада 2023, 21:25
У лекції наведено приклад коду:
Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;
який, згідно лекції, повертає в консоль true. Але ж оскільки у класу Object метод equals порівнює ссилки на об'єкти (==) , а в цьому коді створюється 2 об'єкти, то змінні a та b мають різні посилання, а отже вивод у консоль повинен бути false. Чи я щось неправильно зрозуміла?
Dmytrii
Рівень 32
24 червня 2024, 13:12
данный код вообще не компилируется
IronMan57
Рівень 1
10 грудня 2024, 14:51
Насправді змінні a та b посилаются на екземпляри класу Integer, так як створюються екземпляри саме цього класу. А в класі Integer метод equals перевизначено.
IronMan57
Рівень 1
10 грудня 2024, 14:59
Dmytro, в мене код компілюється і працює, але при компіляції видається таке повідомлення - "Integer(int) in Integer has been deprecated and marked for removal" (був визнаний застарілим та позначено для видалення). В мене встановлена 17-а версія Java. Можливо, у вас версія новіша, і цей функціонал вже було видалено. З цього прикладу можна побачити, що матеріал в актуальному стані не підтримується вже багато років.
Pavlo Kezin
Рівень 23
24 серпня 2023, 21:53
Валідатор не пропускав задачу через використання a.equals(b) замість Objects.equals(a, b).... щось я не памятаю, що ми це проходили... взагалі, лекція розроблена жахливо. Стала хоч щось зрозуміло тільки після інших статтей на цьому ж сайті.
Viacheslav B. System Administrator
20 лютого 2024, 18:25
так десь на початку , мабуть у лекції про корисну літературу вони говорили що треба користуватися додаткової літературою, тобто рішення деяких задач треба шукати у іншому місці.
25 лютого 2024, 09:28
Когда я пытался учиться по программе ЕРАМ, там тоже смеялись как дети, типа интернет большой. Тут я заплатил деньги за то, чтобы мне дали базовую информацию и закрепили задачами. А не снова тот стиль, мол интернет большой. Вопрос риторический, а зачем такое обучение если интернет большой? Наверное, чтобы сэкономить время и ресурсы??? Научив читать, я дальше как-то сам..... А не на тебе азбуку и вперед...
Antek_Ukraine
Рівень 14
Expert
9 липня 2023, 13:44
А мені задача з оверрайдом hashCode () спочатку не заходила, аж півгодини думав. А потім як зайшла) вирішується однією строкою. подумайте, як за допомогою суми двох чисел (підказка) перевірити, чи РІВНІ строка та інт двох окремо взятих елементів.
mr.Coder
Рівень 35
20 червня 2023, 14:56
Objects.hash(model, year); Хіба цей метод десь упоминався раніше в лекціях?
Max
Рівень 20
7 липня 2023, 17:29
Про те як реалізувати hash гуглив інфу в інтернеті, і там було не так, цей варіант побачив лише в обговоренні задачі і тут
Олег
Рівень 111
Expert
17 квітня 2023, 21:07
Дивлюсь на другу задачу і думаю ... вірніше не думаю - просто уважно дивлюсь ... і вона на мене дивиться... і між нами сірий шум.