Сравнение объектов Java с помощью equals() и hashcode()

Какова связь между equals() и hashcode()? Узнайте, как эти методы работают вместе при сравнении объектов в Java.

В этой статье вы узнаете, как объединить equals() и hashcode(). Работая вместе,эти методы проверяют, имеют ли два объекта одинаковые значения.

Без equals() и hashcode() нам пришлось бы создавать громоздкие сравнения «if», сопоставляя каждое поле с объектом. Что сделало бы исходный код запутанным.

Скачать исходный код.

Содержание

Переопределение equals() и hashcode() в Java

Переопределение — это способ, при котором поведение родительского класса или интерфейса повторно прописывается (переопределяется) в подклассе. Каждый Object в Java включает в себя метод equals() и hashcode(). Но для корректной работы они должны быть переопределены.

Ниже приведен метод equals() в классе Object. Метод проверяет, совпадает ли текущий экземпляр с ранее переданным объектом.

publicboolean equals(Objectobj){
return(this==obj);
}

Если hashcode() не переопределен, будет вызван метод, используемый по умолчанию в классе Object. Это означает, что он будет выполнен на другом языке, таком как C, и вернет некоторый результат относительно адреса памяти объекта.

@HotSpotIntrinsicCandidate
publicnativeinthashCode();

Если equals() и hashcode() не переопределены, вместо них вы увидите приведенные выше методы. В этом случае методы не выполняют задачу equals() и hashcode()— проверку, имеют ли два (или более) объекта одинаковые значения.

Сравнение объектов с помощью метода equals ()

Мы используем метод equals() для сравнения объектов в Java. Чтобы определить, совпадают ли два объекта, equals() сравнивает значения атрибутов объектов:

publicclassEqualsAndHashCodeExample{

publicstaticvoid main(String...equalsExplanation){
System.out.println(newSimpson("Homer",35,120)
.equals(newSimpson("Homer",35,120)));

System.out.println(newSimpson("Bart",10,120)
.equals(newSimpson("El Barto",10,45)));

System.out.println(newSimpson("Lisa",54,60)
.equals(newObject()));
}
	
staticclassSimpson{

privateString name;
privateint age;
privateintweight;

publicSimpson(String name,int age,intweight){
this.name = name;
this.age= age;
this.weight=weight;
}

@Override
publicboolean equals(Object o){
if(this== o){
returntrue;
}
if(o ==null||getClass()!=o.getClass()){
returnfalse;
}
Simpsonsimpson=(Simpson) o;
return age ==simpson.age&&
weight==simpson.weight&&
name.equals(simpson.name);
}
}

}

В первом случае equals() сравнивает текущий экземпляр объекта с переданным объектом. Если оба имеют одинаковые значения, equals() вернет true.

Во втором сравнении equals() проверяет, является ли переданный объект пустым, или его тип принадлежит к другому классу. Если это другой класс, то объекты не равны.

Наконец, equals() сравнивает поля объектов. Если два объекта имеют одинаковые значения полей, то объекты одинаковы.

Анализ сравнений объектов

Рассмотрим результаты этих сравнений в методе main(). Сначала мы сравниваем два объекта Simpson:

System.out.println(newSimpson("Homer",35,120).equals(newSimpson("Homer",35,120)));

Объекты идентичны, поэтому результат будет true.

Затем снова сравниваем два объекта Simpson:

System.out.println(newSimpson("Bart",10,45).equals(newSimpson("El Barto",10,45)));

Объекты почти идентичны, но поля name имеют разные значения (Bart и El Barto). Поэтому результат будет false.

Сравним объект Simpson и экземпляр класса Object:

System.out.println(newSimpson("Lisa",54,60).equals(newObject()));

В этом случае результат будет false, потому что типы классов разные.

equals () или ==

Может показаться, что оператор == и метод equals() делают то же самое. Но на самом деле они работают по-разному. Оператор == сравнивает, указывают ли две ссылки на один и тот же объект.

Например:

System.out.println(homer == homer2);

В первом сравнении мы создали два разных экземпляра Simpson, используя оператор new. Из-за этого переменные homer и homer2 будут указывать на разные объекты в памяти. В результате мы получим false.

System.out.println(homer.equals(homer2));

Во втором сравнении мы переопределяем метод equals(). В этом случае будут сравниваться только поля name. Поскольку name обоих объектов Simpson является «Homer», результат true.

Идентификация объектов с помощью hashcode ()

Мы используем метод hashcode() для оптимизации производительности при сравнении объектов. Выполнение hashcode() возвращает уникальный идентификатор для каждого объекта в программе. Что значительно облегчает реализацию.

Если хэш-код объекта не совпадает с хэш-кодом другого объекта, нет причин для выполнения метода equals(). Вы просто будете знать, что два объекта не совпадают. Но если хэш-код одинаков, то нужно выполнить equals(), чтобы определить, совпадают ли значения и поля объектов.

Практический пример использования hashcode().

publicclassHashcodeConcept{

publicstaticvoid main(String...hashcodeExample){
Simpson homer =newSimpson(1,"Homer");
Simpsonbart=newSimpson(2,"Homer");

booleanisHashcodeEquals=homer.hashCode()==bart.hashCode();

if(isHashcodeEquals){
System.out.println("Should compare with equals method too.");
}else{
System.out.println("Should not compare with equals method because "+
"the id is different, thatmeans the objects are not equals for sure.");
}
}

staticclassSimpson{
int id;
String name;

publicSimpson(int id,String name){
this.id = id;
this.name = name;
}

@Override
publicboolean equals(Object o){
if(this== o)returntrue;
if(o ==null||getClass()!=o.getClass())returnfalse;
Simpsonsimpson=(Simpson) o;
return id == simpson.id &&
name.equals(simpson.name);
}

@Override
publicinthashCode(){
return id;
}
}
}

hashcode(), который всегда возвращает одно и то же значение, не очень эффективен. В этом случае сравнение всегда будет возвращать true, поэтому метод equals() будет выполняться всегда. Поэтому улучшить производительность кода не получится.

Использование equals() и hashcode() с коллекциями

Интерфейс Set отвечает за то, чтобы в подкласс Set не было повторяющихся элементов. Ниже перечислены часто используемые классы, реализующие интерфейс Set:

  • HashSet
  • TreeSet
  • LinkedHashSet
  • CopyOnWriteArraySet

В Set можно использовать только уникальные элементы. Поэтому, если вы хотите добавить элемент в класс HashSet, сначала необходимо использовать equals() и hashcode(). Но если эти методы не будут переопределены, вы рискуете вставить в код повторяющиеся элементы.

В приведенном ниже примере для добавления нового элемента в объект HashSet используется метод add. Перед добавлением нового элемента HashSet проверяет, существует ли элемент в данной коллекции:

if(e.hash==hash&&((k =e.key)==key||(key!=null&&key.equals(k))))
break;
       p = e;

Если объект тот же, новый элемент не будет вставлен.

Хэш-коллекции

Set — это не единственная коллекция, которая использует equals() и hashcode(). HashMap, Hashtable и LinkedHashMap также требуют применения этих методов. Если коллекция с префиксом «Hash», то она требует переопределения методов equals() и hashcode().

Рекомендации по использованию equals() и hashcode()

Для объектов, имеющих один и тот же уникальный идентификатор hashcode, нужно использовать только equals(). Не нужно выполнять equals(), когда идентификатор hashcode отличается.

Таблица 1. Сравнение хэш-кодов

Если сравнение hashcode() Тогда …
возвращает true выполнить equals ()
возвращает false не выполнять equals ()

Этот принцип используется в коллекциях Set или Hash для повышения производительности.

Правила сравнения объектов

Когда сравнение hashcode() возвращает false, метод equals() также должен возвращать значение false. Если хэш-код отличается, то объекты не равны.

Таблица 2. Сравнение объектов с помощью hashcode()

Когда сравнение хэш-кодов возвращает… метод equals() должен возвращать …
True true или false
False False

Когда метод equals() возвращает true, то объекты равны во всех значениях и атрибутах. В этом случае сравнение хэш-кодов должно возвращать true.

Таблица 3. Сравнение объектов с помощью equals()

Когда метод equals() возвращает … метод hashcode() должен возвращать…
True True
False true или false

Выполните задание на использование equals() и hashcode()!

Пришло время проверить свои навыки. Нужно получить результат двух сравнений метода equals() и узнать размер коллекции Set.

Для начала внимательно изучите приведенный ниже код:

publicclassEqualsHashCodeChallenge{

publicstaticvoid main(String...doYourBest){
System.out.println(newSimpson("Bart").equals(newSimpson("Bart")));
Simpson overriddenHomer =newSimpson("Homer"){
publicinthashCode(){
return(43+777)+1;
}
};

System.out.println(newSimpson("Homer").equals(overriddenHomer));

Setset=newHashSet(Set.of(newSimpson("Homer"),newSimpson("Marge")));
set.add(newSimpson("Homer"));
set.add(overriddenHomer);

System.out.println(set.size());
}

staticclassSimpson{
String name;

Simpson(String name){
this.name = name;
}

@Override
publicboolean equals(Objectobj){
SimpsonotherSimpson=(Simpson)obj;
returnthis.name.equals(otherSimpson.name)&&
this.hashCode()==otherSimpson.hashCode();
}

@Override
publicinthashCode(){
return(43+777);
}
}

}

Проанализируйте код, угадайте результат, а затем запустите программу. Ваша цель заключается в том, чтобы улучшить навыки анализа кода,усвоить основные концепции Java и сделать создаваемый код более эффективным. Выберите свой вариант, прежде чем проверять правильный ответ, который приведен ниже.

A)
true
true
4

B)
true
false
3

C)
true
false
2

D)
false
true
3

Что сейчас произошло? Понимание equals() и hashcode()

В первом сравнении equals() результат верен, потому что метод hashcode() возвращает одно и то же значение для обоих объектов.

Во втором случае метод hashcode() переопределяется для переменной overridenHomer. Поле name в обоих объектах Simpson- “Homer”. Но метод hashcode() возвращает другое значение для overriddenHomer. В этом случае результат работы equals() будет false, поскольку метод содержит сравнение с хэш-кодом.

Обратите внимание, что размер коллекции задан для хранения трех объектов Simpson. Рассмотрим это более подробно.

Первый объект в наборе будет добавлен в коллекцию:

newSimpson("Homer");

Следующий объект также будет добавлен, поскольку имеет отличное от предыдущего объекта значение:

newSimpson("Marge");

Последний объект Simpson имеет то же значение, что и первый. В этом случае объект не будет вставлен:

set.add(newSimpson("Homer"));

Объект overridenHomer использует другое значение хэш-кода из обычного экземпляра Simpson («Homer»). По этой причине этот элемент будет вставлен в коллекцию:

overriddenHomer;

Ответ

Результат:

true
false
3

Распространенные ошибки использования equals() и hashcode()

  • Отсутствие переопределения hashcode() вместе с методом equals() или наоборот.
  • Не переопределенные equals() и hashcode() при использовании хеш-коллекций, таких как HashSet.
  • Возврат константного значения в методе hashcode() вместо уникального кода для каждого объекта.
  • Использование == и equals взаимозаменяемо. == сравнивает ссылки на объекты, тогда как equals() сравнивает значения объектов.

Что нужно помнить о equals() и hashcode()

  • Рекомендованная практика — всегда переопределять методы equals() и hashcode() в POJO.
  • Используйте эффективный алгоритм для генерации уникального хэш-кода.
  • При переопределении метода equals() всегда переопределяйте hashcode().
  • Метод equals() должен сравнивать все значения полей.
  • Метод hashcode() может быть идентификатором POJO.
  • Если equals() и hashcode() не переопределяются при использовании хэш-коллекций, коллекция будет иметь повторяющиеся элементы.

Данная публикация представляет собой перевод статьи «Java Challengers #4: Comparing Java objects with equals() and hashcode()» , подготовленной дружной командой проекта Интернет-технологии.ру

Меню