Перегрузка методов equals() и hashCode() в Java
Переопределение методов equals() и hashCode() в 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
Проверьте объект на null , а также проверьте, чтобы объекты были одного типа. Не делайте проверку с помощью instanceof так как такая проверка будет возвращать true для подклассов и будет работать правильно только в случае если ваш класс объявлен как immutable . Вместо этого можно использовать getClass() ;
Объявите переменную типа, который вы сравниваете, и приведите obj к этому типу. Потом сравнивайте каждый атрибут типа начиная с численных атрибутов (если имеются) потому что численные атрибуты проверяются быстрей. Сравнивайте атрибуты с помощью операторов И и ИЛИ (так называемые short-circuit logical operators ) для объединения проверок с другими атрибутами.
Распространенные ошибки при переопределении 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() . Ниже представлен корректный код.
Третья распространенная ошибка это не переопределять метод 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 .
Программирование на C, C# и Java
Уроки программирования, алгоритмы, статьи, исходники, примеры программ и полезные советы
ОСТОРОЖНО МОШЕННИКИ! В последнее время в социальных сетях участились случаи предложения помощи в написании программ от лиц, прикрывающихся сайтом vscode.ru. Мы никогда не пишем первыми и не размещаем никакие материалы в посторонних группах ВК. Для связи с нами используйте исключительно эти контакты: vscoderu@yandex.ru, https://vk.com/vscode
Метод equals в Java
Часто при программировании нам приходиться проверять равенство объектов. Но как же работает сравнение объектов в языке Java? Расскажем вам в этой статье.
Статья ориентирована на читателей среднего уровня подготовки Java.
Сравнение с помощью == и equals в Java
Приведем пример программного кода:
Но, в чем же разница между этими двумя записями? Какое отличие между оператором сравнения == и методом equals?
Все очень просто. Метод equals в Java при сравнении проверяет и сопоставляет само содержимое объектов (их значения) и на основе этого делает заключение равны они (true) или нет (false).
Оператор == (в случае с примитивными типами данных) сравнивает значения переменных и возвращает результат, НО в случае со ссылочными типами данных (объекты, массивы и т.д.) сравнивает ссылки на объекты в памяти компьютера, и на основании равенства или неравенства ссылок возвращает результат (true или false). Вот в чём отличие метода equals и оператора ==.
Вы можете почитать подробную статью про оба вида типов данных и их различия в соответствующей статье.
Метод equals — это метод класса Object. Каждый объект неявно унаследован от класса Object и они могут вызывать метод equals.
Возвращаясь к примеру, приведенному в начале подраздела, можно сделать вывод о том, каким будет результат операции сравнения в обоих случаях:
Java Challengers #4: Сравнение объектов с equals() и hashCode()
В этой статье вы узнаете, как связаны между собой методы equals() и hashCode() и как они используются при сравнении объектов.

Без использования equals() и hashCode() для сравнения состояния двух объектов нам нужно писать много сравнений » if «, сравнивая каждое поле объекта. Такой подход делает код запутанным и трудным для чтения. Работая вместе, эти два метода помогают создавать более гибкий и согласованный код.
Исходный код для статьи находится здесь.
Переопределение equals() и hashCode()
Переопределение метода (method overriding) — это приём при котором поведение родительского класса или интерфейса переписывается (переопределяется) в подклассе (см. Java Challengers #3: Полиморфизм и наследование, анг.). В Java у каждого объекта есть методы equals() и hashCode() и для правильной работы они должны быть переопределены.
Чтобы понять, как работает переопределение equals() и hashCode() , изучим их реализацию в базовых классах Java. Ниже приведён метод equals() класса Object . Метод проверяет, совпадает ли текущий экземпляр с переданным объектом obj .
Теперь посмотрим на метод hashCode() в классе Object .
Это native — метод, который написан на другом языке, таком как Си, и он возвращает некоторый числовой код, связанный с адресом памяти объекта. (Если вы не пишете код JDK, то не важно точно знать, как работает этот метод.)
Примечание переводчика: про значение, связанное с адресом сказано не совсем корректно (спасибо vladimir_dolzhenko). В HotSpot JVM по умолчанию используются псевдослучайные числа. Описание реализации hashCode() для HotSpot, есть здесь и здесь.
Если методы equals() и hashCode() не переопределены, вместо них будут вызваны методы класса Object , описанные выше. В этом случае методы не выполняют реальной цели equals() и hashCode() , которая состоит в том, чтобы проверить, имеют ли объекты одинаковые состояния.
Как правило, при переопределении equals() также переопределяется hashCode() .
Сравнение объектов с equals()
Метод equals() используется для сравнения объектов. Чтобы определить одинаковые объекты или нет, equals() сравнивает значения полей объектов:
Посмотрим на метод equals() . Первое сравнение сравнивает текущий экземпляр объекта this с переданным объектом o . Если это один и тот же объект, то equals() вернёт true .
Во втором сравнении проверяется, является ли переданный объект null и какой у него тип. Если переданный объект другого типа, то объекты не равны.
Наконец, equals() сравнивает поля объектов. Если два объекта имеют одинаковые значения полей, то объекты совпадают.
Анализ вариантов сравнения объектов
Теперь давайте посмотрим на варианты сравнений объектов в методе main() . Сначала мы сравниваем два объекта Simpson :
У полей этих объектов одинаковые значения, поэтому результат будет true .
Затем снова сравниваем два объекта Simpson :
Объекты здесь похожи, но значения имён разные: Bart и El Barto. Поэтому результат будет false .
Наконец, давайте сравним объект Simpson и экземпляр класса Object :
В этом случае результат будет false , так как типы объектов отличаются.
equals() в сравнении с ==
На первый взгляд кажется, что оператор == и метод equals() делают одно и то же, но, на самом деле, они работают по-разному. Оператор == сравнивает, указывают ли две ссылки на один и тот же объект. Например:
Мы создали два разных экземпляра Simpson с помощью оператора new . Поэтому переменные homer и homer2 будут указывать на разные объекты в куче. Таким образом, в результате получим false .
Во следующем примере используем переопределенный метод equals() :
В этом случае будут сравниваться поля. Поскольку значения полей у обоих объектов Simpson одинаковые, результат будет true .
Идентификация объектов с hashCode()
Для оптимизации производительности при сравнении объектов используется метод hashCode() . Метод hashCode() возвращает уникальный идентификатор для каждого объекта, что упрощает сравнение состояний объектов.
Если хэш-код объекта не совпадает с хэш-кодом другого объекта, то можно не выполнять метод equals() : вы просто знаете, что два объекта не совпадают. С другой стороны, если хэш-код одинаковый то, необходимо выполнить метод equals() , чтобы определить, совпадают ли значения полей.
Рассмотрим практический пример с hashCode() .
Метод hashCode() , который всегда возвращает одно и то же значение, допустим, но не эффективен. В этом случае сравнение всегда будет возвращать true , поэтому метод equals() будет выполняться всегда. В этом случае нет никакого улучшения производительности.
Использование equals() и hashCode() с коллекциями
Классы, реализующие интерфейс Set (множество) должны не допускать добавления повторяющихся элементов. Ниже приведены некоторые классы, реализующие интерфейс Set :
В Set могут быть добавлены только уникальные элементы. Таким образом, если вы хотите добавить элемент, например, в HashSet , вы должны использовать сначала методы equals() и hashCode() , чтобы убедиться, что этот элемент уникальный. Если методы equals() и hashCode() не были переопределены, вы рискуете вставить повторяющиеся значения.
Посмотрим на часть реализации метода add() в HashSet :
Перед добавлением нового элемента HashSet проверяет, существует ли элемент в данной коллекции. Если объект совпадает, то новый элемент вставляться не будет.
Методы equals() и hashCode() используются не только в Set . Также эти методы требуются для HashMap, Hashtable, и LinkedHashMap. Как правило, если вы видите коллекцию с префиксом «Hash», вы можете быть уверены, что для её корректной работы требуется переопределение методов hashCode() и equals() .
Рекомендации по использованию equals() и hashCode()
Выполняйте метод equals() только для объектов с одинаковым хэш-кодом. Не выполняйте equals() , если хэш-код отличается.
Таблица 1. Сравнение хэш-кодов
| Если сравнение hashCode() . | То . |
|---|---|
| возвращает true | выполнить equals() |
| возвращает false | не выполнять equals() |
Этот принцип в основном используется в коллекциях Set или Hash по соображениям производительности.
Правила сравнения объектов
Когда сравнение hashCode() возвращает false , метод equals() также должен возвращать false . Если хэш-код отличается, то объекты определенно не равны.
Таблица 2.Сравнение объектов с hashCode()
| Когда сравнение hashCode() возвращает . | Метод equals() должен вернуть . |
|---|---|
| true | true или false |
| false | false |
Когда метод equals() возвращает true , это означает, что объекты равны во всех значениях и атрибутах. В этом случае сравнение хэш-кода также должно быть истинным.
Таблица 3. Сравнение объектов с equals()
| Когда метод equals() возвращает . | Метод hashCode() должен вернуть . |
|---|---|
| true | true |
| false | true или false |
Решите задачку на equals() и hashCode()
Пришло время проверить ваши знания методов equals() и hashCode() . Задача состоит в том, чтобы выяснить результат нескольких equals() и итоговый размер коллекции Set .
Для начала, внимательно изучите следующий код :
Сначала проанализируйте код, подумайте, какой будет результат. И только потом запустите код. Цель в том, чтобы улучшить ваши навыки анализа кода и усвоить основные концепции Java, чтобы вы могли сделать свой код лучше.
Какой будет результат?.
Что произошло? Понимание equals() и hashCode()
В первом сравнении результат equals() равен true , поскольку состояния объектов одинаковые, и метод hashCode() возвращает одно и то же значнеие для обоих объектов.
Во втором сравнении для переменной overriddenHomer был переопределён метод hashCode() . Для обоих объектов Simpson имя равно «Homer», но для overriddenHomer метод hashCode() возвращает другое значение. В этом случае результат метода equals() будет false , так как в нём содержится сравнение с хэш-кодом.
Вы, должно быть, поняли, что в коллекции будет три объекта Simpson . Давайте разберём это.
Первый объект в наборе будет вставлен как обычно:
Следующий объект также будет вставлен в обычном порядке, поскольку содержит значение, отличное от предыдущего объекта:
Наконец, следующий объект Simpson имеет то же значение имени, что и первый объект. В этом случае объект вставляться не будет:
Как мы знаем, объект overridenHomer использует другое значение хэш-кода в отличие от обычного экземпляра Simpson(«Homer») . По этой причине этот элемент будет вставлен в коллекцию:
Ответ
Правильный ответ — B. Вывод будет:
Частые ошибки с equals() и hashCode()
- Отсутствие переопределения hashCode() вместе с переопределением equals() или наоборот.
- Отсутствие переопределения equals() и hashCode() при использовании хэш-коллекций, таких как HashSet .
- Возврат постоянного значения в методе hashCode() вместо возврата уникального кода для каждого объекта.
- Равнозначное использование == и equals() . Оператор == сравнивает ссылки на объекты, тогда как метод equals() сравнивает значения объектов.
Что нужно помнить о equals() и hashCode()
- Рекомендуется всегда переопределять методы equals() and hashCode() в ваших POJO (рус., анг.)
- Используйте эффективный алгоритм для создания уникального хэш-кода.
- При переопределении метода equals() всегда переопределяйте метод hashCode() .
- Метод equals() должен сравнивать полное состояние объектов (значения из полей).
- Метод hashCode() может быть идентификатором (ID) POJO.
- Если результат сравнения хэш-кода двух объектов false , то метод equals() также должен иметь значение false .
- Если equals() и hashCode() не переопределяются при использовании хэш-коллекций, то коллекция будет иметь повторяющиеся элементы.
Изучите больше о Java
- Посмотрите больше примеров работы с equals() и hashCode() (анг.).
- См. Java Dev Gym и NoBugsProject от Рафаэля Дел Неро (Rafael Del Nero).
Традиционно жду ваши комментарии и приглашаю на открытый урок, который уже 18 марта проведет наш преподаватель Сергей Петрелевич
Name already in use
JBook / object / equals.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
В основном мы оперируем объектами, и логично было бы уметь как-то сравнивать их между собой.
В Java существует два вида сравнения:
- По ссылке
- По значению
Сравнение по ссылке происходит тогда, когда вы используете оператор == .
Для демонстрации давайте посмотрим на следующий пример:
Ссылки my и your указывают на разные объекты, в то время как link ссылается на тот же объект, на который ссылается my .
Результат выполнения кода:
Из примера выше видно, что == сравнивает не свойства объектов, не состояние, а ссылки на объекты. Такой способ сравнения подходит, если мы сравниваем уникальные объекты, существующие в одном экземпляре.
Но чаще всего это не то, что требуется, так как обычно при сравнении необходимо, чтобы сравнивались не ссылки, а состояния объектов. При этом, не всегда требуется, чтобы сравнивались все свойства объектов. Ведь логика сравнения может быть своя. Например, мы можем сказать, что две машины равны, если они одинаково стоят. Но при этом у машины есть еще другие свойства, такие как цвет, тип кузова и т.д.
Для этого и существует сравнение по значению, за которое отвечает метод equals . Метод наследуется от класса java.lang.Object , а значит присутствует у каждого класса.
Данный метод сравнивает два объекта и возвращает true в случае, если объекты равны, в противном случае будет возвращено false .
По умолчанию объявление метода в java.lang.Object выглядит так:
Т.е по-умолчанию производится сравнение по ссылке.
Поэтому данный метод всегда переопределяют для классов, объекты которого необходимо уметь сравнивать по состоянию.
Так как же правильно определить equals ?
Требования из описания из JavaDoc :
Для любой ссылки на значение х выражение х.equals(x) должно возвращать true .
Для любых ссылок на значения х и у выражение х.equals(y) должно возвращать tгue тогда и только тогда, когда y.equals(x) возвращает true .
Транзитивность Для любых ссылок на значения х , у и z , если x.equals(y) возвращает true и y.equals(z) возвращает true , то и выражение х.equals(z) должно возвращать true .
Непротиворечивость или Согласованность
Для любых ссылок на значения х и у , если несколько раз вызвать х.equals(y) , постоянно будет возвращаться значение true , либо постоянно будет возвращаться значение false при условии, что никакая информация, используемая при сравнении объектов, не поменялась.
Для любой ненулевой ссылки на значение х выражение х.equals(null) должно возвращать false .
При переопределении equals необходимо переопределить и hashCode.
Как переопределить equals
Пример приведем с помощью многострадального класса Person :
Итак, нам необходимо переопределить equals , напомним как выглядит сигнатура метода:
В качестве аргумента метода передается java.lang.Object , это ссылочный тип, а значит может быть передан null . Поэтому сначала необходимо проверить: а не является ли obj null -ом, чтобы обезопасить себя от java.lang.NullPointerException , если да, то возвращаем false .
После этого, хорошо бы сделать проверку, а не ссылается ли объект по ссылке obj , на наш текущий, на this ? Ведь если так, то объекты равны и никаких дальнейших сравнений делать не надо.
Напишем все вышесказанное:
Теперь ответим на вопрос: а может ли объект по ссылке obj другого класса? Может, ведь ссылка у нас Object ! А значит, надо проверить: мы сравниваем объекты одного класса?
После этого ссылку obj можно безопасно скастовать к нашему классу и сделать сравнение полей:
В итоге после всего вышесказанного наш класс будет выглядеть так:
С Java 7+ добавили вспомогательные классы и методы, используя которые можно переписать наш equals более коротко:
Однако, в Java есть еще один способ проверить принадлежность к классу: через оператор instanceOf .
getClass vs instanceOf
Как работает getClass? Если коротко — то метод возвращает класс объекта.
Что делает оператор instanceOf ? Оператор проверяет то, является ли объект инстансом конкретного класса или одним из его родителей.
В чем разница? При использовании getClass вы можете проверить только принадлежность к определенному классу и не более. При использовании instanceOf вы можете проверить как принадлежность к классу, так и к родительским классам.
Когда что использовать? Зависит от ответа на вопрос: будет ли ваш класс участвовать в сравнении с родительскими классами?
И как только появляется необходимость сравнения с родительскими классами, начинается проблема. Почему?
Итак, рассмотрим следующий пример:
На первый взгляд, вроде бы все верно. Однако, такая реализация нарушает требование симметричности:
При этом, если переопределить метод с помощью getClass , как это мы сделали ранее, то нарушается Liskov substitution principle:
Объекты могут быть заменены их наследниками без изменения свойств программы.
А это может привести к крайне неприятному поведению в HashMap -ах и прочих hash -структурах:
The reason that I favor the instanceof approach is that when you use the getClass approach, you have the restriction that objects are only equal to other objects of the same class, the same run time type. If you extend a class and add a couple of innocuous methods to it, then check to see whether some object of the subclass is equal to an object of the super class, even if the objects are equal in all important aspects, you will get the surprising answer that they aren’t equal. In fact, this violates a strict interpretation of the Liskov substitution principle, and can lead to very surprising behavior. In Java, it’s particularly important because most of the collections (HashTable, etc.) are based on the equals method. If you put a member of the super class in a hash table as the key and then look it up using a subclass instance, you won’t find it, because they are not equal.
В итоге получается палка о двух концах, поэтому выбор getClass или instanceOf надо делать под конкретную задачу, осознавая к чему может привести ваше решение.
Возможно, стоит вообще отказаться от наследования.
Но в некоторых случаях, instanceOf вполне можно использовать и не переживать о нарушениях. Например, посмотрите как определен equals у HashSet :
Здесь использован instanceOf , так как если все элементы одного множества содержатся в другом — множества считаются равными, независимо от того, это HashSet или TreeSet .
Забегая вперед скажу, что, хоть иногда и необходим явно instanceOf , в подавляющем большинстве случаев вы будете использовать getClass и горя не знать.
Обратите внимание, что тип аргумента в методе — это java.lang.Object :
Именно поэтому в реализации equals есть строки:
И часто у начинающих разработчиков возникает желание указать более конкретный аргумент в equals , чтобы избавиться от этой проверки и приведения к Person , поэтому некоторые пишут так:
Разумеется, делать так не стоит, если ваша цель переопределить метод. Потому что в случае выше вы не переопределили метод, вы его перегрузили.
В итоге у вас получается два метода: equals(Object obj) и equals(Person obj) . Про второй метод знаете только вы, ведь он не относится к java.lang.Object , поэтому везде будет также использоваться реализация по умолчанию equals(Object obj) , например, в hash -таблицах, кроме ситуаций, где вы явно свой метод не вызываете.
Делать так не стоит, так как это вносит путаницу, работает такое сравнение только там, где вы его явно вызываете, а в коллекциях будет использоваться equals(Object obj) . Поэтому надо переопределять такие методы, а не перегружать их.
При этом надо отметить, что аннотация @Override предотвратит от такой ошибки не дав скомпилировать код, поэтому, когда переопределяете методы не забывайте ее ставить.
Массивы и equals
У массивов не переопределен equals и выполняется сравнение ссылок.
Решением является использовать статический метод для сравнения массивов: Arrays.equals(. ) .
Для сравнения объектов по значению необходимо переопределять метод equals , при этом выполняя требования к методу: рефлективность, симметричность, транзитивность, согласованность. Всегда с equals переопределяйте еще и hashCode .
Помните о разнице instanceOf и getClass и выбирайте использование под задачу.
При работе с float и double помните о том, что существуют Float.NaN , поэтому для сравнения таких типов используйте специальные методы Float.compare и Double.compare .
Для простых полей, кроме double и float используйте обычное сравнение через == .
Не забывайте, что очередность сравнения влияет на производительность, поэтому сначала сравниваем поля, которые чаще других могут быть различны.
Не забывайте, что метод принимает java.lang.Object , поэтому изменение сигнатуры метода — это не переопределение и о таком методе будете знать только вы.
Объявление equals в виде
Является ошибкой, так как этот метод не переопределяет(override) equals у java.lang.Object , а перегружает(overload) его.
Помните, что большинство IDE сейчас легко сгенерируют вам equals , чтобы вы не писали его вручную.
Также, существуют сторонние проекты, которые берут кодогенерацию на себя, например, проект lombok.
Существуют и сторонние библиотеки, помогающие в вычислении equals , например apache commons.