Перевизначення методів equals() та hashCode() в Java
Equals
і hashCode
є фундаментальними методами, оголошені в класі Object
і містяться в стандартних бібліотеках Java. Метод еquals()
використовується для порівняння об'єктів, а hashCode
- для генерації цілого коду об'єкта. Ці методи широко використовуються в стандартних бібліотеках Java при вставці та вийманні об'єктів у HashMap
. Метод equal
також використовується для забезпечення зберігання тільки унікальних об'єктів HashSet
та інших Set
реалізаціях, а також у будь-яких інших випадках, коли потрібно порівнювати об'єкти. Реалізація за умовчанням методу equals()
в класі java.lang.Object
порівнює посилання на адресаи в пам'яті, які зберігають змінні, та повертаєtrue
Тільки в тому випадку, якщо адресаи збігаються, тобто змінні посилаються на той самий об'єкт. Java рекомендує перевизначати методи equals()
і hashCode()
, якщо передбачається, що порівняння має здійснюватися відповідно до природної логіки або бізнес-логіки. Багато класів у стандартних бібліотеках Java перевизначають їх, наприклад у класі String
перевизначається equals
таким чином, що повертається true
, якщо вміст двох об'єктів, що порівнюються, однаковий. У класі-обгортці Integer
метод equal
перевизначається для виконання чисельного порівняння і так далі. Так як HashMap
і HashTable
в Java покладаються на методи equals()
і hashCode()
для порівняння своїх key
іvalues
Java пропонує такі правила для перевизначення цих методів:
- Рефлексивність: Об'єкт повинен дорівнювати собі самому.
- Симетричність: якщо
a.equals(b)
повертаєtrue
, тоb.equals(a)
повинен також повернутиtrue
. - Транзитивність: якщо
a.equals(b)
повертаєtrue
іb.equals(c)
теж повертаєtrue
, тоc.equals(a)
теж має повертатиtrue
. - Узгодженість: повторний виклик методу
equals()
повинен повертати те саме значення до тих пір, поки якесь значення властивостей об'єкта не буде змінено. Тобто, якщо два об'єкти дорівнюють Java, то вони будуть рівні поки їх властивості залишаються незмінними. - Порівняння
null
: об'єкт повинен бути перевірений наnull
. Якщо об'єкт дорівнюєnull
, то метод повинен повернутиfalse
, а неNullPointerException
. Наприклад,a.equals(null)
має повернутиfalse
.
Угода між equals і hashCode у Java
- Якщо об'єкти дорівнюють за результатами виконання методу
equals
, тоді їхhashcode
мають бути однаковими. - Якщо об'єкти не рівні за результатами виконання методу
equals
, їхhashcode
можуть бути як однаковими, і різними. Однак для підвищення продуктивності краще, щоб різні об'єкти повертали різні коди.
Як перевизначати метод equals у Java
-
@Override public boolean equals(Object obj) { /*1. Перевірте*/
if (obj == this) { /*і поверніть */ return true; }
-
Перевірте об'єкт на
null
, а також перевірте, щоб об'єкти були одним типом. Не робіть перевірку за допомогоюinstanceof
так як така перевірка буде повертатиtrue
для підкласів і працюватиме правильно тільки у випадку, якщо ваш клас оголошений якimmutable
. Замість цього можна використовуватиgetClass()
;if (obj == null || obj.getClass() != this.getClass()) { return false; }
-
Оголосіть змінну типу, який ви порівнюєте, та приведіть
obj
до цього типу. Потім порівнюйте кожен атрибут типу з чисельних атрибутів (якщо є) оскільки чисельні атрибути перевіряються швидше. Порівнюйте атрибути за допомогою операторів І та АБО (так званіshort-circuit logical operators
) для об'єднання перевірок з іншими атрибутами.Person guest = (Person) obj; return id == guest.id && (firstName == guest.firstName || (firstName != null && firstName.equals(guest.getFirstName()))) && (lastName == guest.lastName || (lastName != null && lastName .equals(guest.getLastName()))); }
/** * Person class with equals and hashcode implementation in Java * @author Javin Paul */
public class Person {
private int id;
private String firstName;
private String lastName;
public int getId() { return id; }
public void setId(int id) { this.id = id;}
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
Person guest = (Person) obj;
return id == guest.id
&& (firstName == guest.firstName
|| (firstName != null &&firstName.equals(guest.getFirstName()))) && (lastName == guest.lastName
|| (lastName != null && lastName .equals(guest.getLastName())
));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode()); result = prime * result + id; result = prime * result +
((lastName == null) ? 0 : lastName.hashCode()); return result;
}
}
Поширені помилки при перевизначенні equals Java
-
Замість того, щоб перевизначати метод
equals (Override)
програміст перевантажує його(Overload)
Синтаксис методуequals()
в класіObject
визначений якpublic boolean equals(Object obj)
, але багато програмістів ненароком перевантажують метод:public boolean equals(Person obj)
- замістьObject
як аргумент використовують ім'я свого класу (напр. Person). Цю помилку складно виявити черезstatic binding
. Таким чином, якщо ви викличете цей метод для об'єкта свого класу, то метод не просто скомпілюється, а зробить це коректно. Однак, якщо ви покладете ваш об'єкт у колекцію, наприклад,ArrayList
і викличете методcontains()
, робота якого заснована на методіequals()
, то методcontains
не зможе виявити ваш об'єкт. -
При перевизначенні методу
equals()
не перевіряти наnull
змінні, що зрештою закінчуєтьсяNullPointerException
при викликіequals()
. Нижче наведено коректний код.firstname == guest.firstname || (firstname != null && firstname.equals(guest.firstname));
-
Третя поширена помилка це не перевизначати метод
hashCode()
, а лишеequals()
. Ви повинні перевизначати обидва способиequals()
іhashCode()
Java. МетодhashCode
використовується вhash
-колекціях(наприкладHashSet
), і чим менше буде колізій (однаковий код при різних об'єктах) тим ефективніше ці колекції працюватимуть з об'єктами вашого класу. -
Остання поширена помилка програмістів у цьому, що з перевизначенні методу
equals()
не зберігається відповідність між методамиequals()
іcompareTo()
, що є неформальним вимогою уникнення зберігання дублікатів вSet (SortedSet, TreeSet)
.
Підказки як писати в Java метод equals
-
Більшість IDE такі як NetBeans, Eclipse та IntelliJ IDEA забезпечують підтримку генерації методів
equals()
таhashCode()
. У Eclipse натисніть праву кнопку -> source ->generate equals()
таhashCode()
. -
Якщо в класі є унікальний бізнес-ключ, то достатньо зробити перевірку тільки на рівність цих полів. Як у нашому прикладі “id” – унікальний номер для кожного Person.
-
При перевизначенні
hashCode()
Java перевірте використання всіх полів, які були використані в методіequals()
. -
String
і класи-оболонки такі якInteger
,Float
іDouble
перевизначають методequals()
, алеStringBuffer
не перевизначає. -
За будь-якої можливості робіть поля
immutable
використовуючиfinal
змінні Java. -
При порівнянні
String
об'єктів використовуйтеequals()
замість оператора==
. -
Два об'єкти, які логічно рівні, але завантажені з різних,
ClassLoader
не можуть бути рівними. Пам'ятайте, що перевірка за допомогоюgetClass()
повернеfalse
, якщо клас-завантажувач різний. -
Використовуйте
@Override
анотацію також для методуhashCode
, оскільки це попереджає невловимі помилки, наприклад значення методу , що повертаєтьсяint
, проте деякі програмісти повертаютьlong
.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ