Как переопределить метод equals в java

Реализация метода equals

Для класса Person со строковыми полями firstName и lastName общий вариант для реализации equals:


Например, предположим, что мы будем реализовывать equals (Person) так:

Посмотрим на простом примере:

equal принимает значение true, а теперь:

Теперь это ложь. Может, не совсем то, что мы ожидали.

Причина в том, что Java вызывает Person.equals(Object) (наследуется от объекта, который проверяет идентичность). Почему?

Стратегия Java в выборе метода основана не на типе среды выполнения параметров, а на его объявленном типе.Поэтому, если Mr Robot объявлен как объект, Java вызывает Person.equals(Object) вместо нашего Person.equals(Person).

Самопроверка

Равенство является фундаментальным свойством любого класса, и оно может в конечном итоге вызываться очень часто, например, в циклах. Таким образом, его производительность имеет значение! И самоконтроль в начале нашей реализации — это просто оптимизация производительности.

java if (this == o) возвращает true;

Может показаться, что он должен реализовывать рефлексивность, но проверки дальше были бы очень странными, если бы они не делали этого.

Проверка NULL значения. Ни один экземпляр не должен быть равен null, поэтому здесь мы убедимся в этом. В то же время он защищает код от Nullpointerexception.

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

Заключение

Мы выяснили разницу между идентичностью (должна быть одна и та же ссылка, проверена с помощью ==) и равенством (могут быть разные ссылки на «одно и то же значение»).

  • Обязательно переопределите equals (Object), чтобы наш метод всегда вызывался.
  • Включите проверку self и null.
  • Используйте getClass, чтобы позволить подтипам выполнять свою собственную реализацию (но не сравнивать по подтипам) или использовать instanceof и make
  • equals final (и подтипы могут быть равны).
  • Сравните нужные поля с помощью Objects.equals.
  • Или пусть ваша IDE генерирует все для вас и редактирует там, где это необходимо.

Оцени статью

Оценить

Средняя оценка / 5. Количество голосов:

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

Спасибо за ваши отзыв!

Как переопределять метод equals в Java

  1. Проверьте объект на , а также проверьте, чтобы объекты были одного типа. Не делайте проверку с помощью так как такая проверка будет возвращать для подклассов и будет работать правильно только в случае если ваш класс объявлен как . Вместо этого можно использовать ;

  2. Объявите переменную типа, который вы сравниваете, и приведите к этому типу. Потом сравнивайте каждый атрибут типа начиная с численных атрибутов (если имеются) потому что численные атрибуты проверяются быстрей. Сравнивайте атрибуты с помощью операторов И и ИЛИ (так называемые ) для объединения проверок с другими атрибутами.

Полный пример переопределения метода equals в Java

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

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

  • HashSet
  • TreeSet
  • LinkedHashSet
  • CopyOnWriteArraySet

В могут быть добавлены только уникальные элементы. Таким образом, если вы хотите добавить элемент, например, в , вы должны использовать сначала методы и , чтобы убедиться, что этот элемент уникальный. Если методы и не были переопределены, вы рискуете вставить повторяющиеся значения.

Посмотрим на часть реализации метода в :

Перед добавлением нового элемента проверяет, существует ли элемент в данной коллекции. Если объект совпадает, то новый элемент вставляться не будет.

Методы и используются не только в . Также эти методы требуются для HashMap, Hashtable, и LinkedHashMap. Как правило, если вы видите коллекцию с префиксом «Hash», вы можете быть уверены, что для её корректной работы требуется переопределение методов и .

Configurability

Through properties on the attribute the following options can be set:

  • => do not weave and operators
  • => do not override the methods
  • => do not override the method, do not add and implement
  • => equality and hash code do not consider properties of base classes.
  • can be used to affect the equality logic.
    • (default): only equal, when the other object is of the same type as . Imagine we have a class with and we have a sub-:
      • may equal
      • may equal
      • but may never equal
    • : only equal, when the other object is of the same as the method is added to. Consider a class with and a sub-:
      • may equal
      • may never equal
      • may never equal
    • : equal, when the other object is of same type as the method is added to, or is of a sub type.

Идентификация объектов с hashCode()

Для оптимизации производительности при сравнении объектов используется метод . Метод возвращает уникальный идентификатор для каждого объекта, что упрощает сравнение состояний объектов.

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

Рассмотрим практический пример с .

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

Переопределение equals() и hashCode()

Переопределение метода (method overriding) — это приём при котором поведение родительского класса или интерфейса переписывается (переопределяется) в подклассе (см. Java Challengers #3: Полиморфизм и наследование, анг.). В Java у каждого объекта есть методы и и для правильной работы они должны быть переопределены.

Чтобы понять, как работает переопределение и , изучим их реализацию в базовых классах Java. Ниже приведён метод класса . Метод проверяет, совпадает ли текущий экземпляр с переданным объектом .

Теперь посмотрим на метод в классе .

Это native — метод, который написан на другом языке, таком как Си, и он возвращает некоторый числовой код, связанный с адресом памяти объекта

(Если вы не пишете код JDK, то не важно точно знать, как работает этот метод.)Примечание переводчика: про значение, связанное с адресом сказано не совсем корректно ( vladimir_dolzhenko). В HotSpot JVM по умолчанию используются псевдослучайные числа

Описание реализации hashCode() для HotSpot, есть здесь и здесь.


Если методы и не переопределены, вместо них будут вызваны методы класса , описанные выше. В этом случае методы не выполняют реальной цели и , которая состоит в том, чтобы проверить, имеют ли объекты одинаковые состояния.

Как правило, при переопределении также переопределяется .

Verifying the Contracts

If we want to check whether our implementations adhere to the Java SE contracts and also to some best practices, we can use the EqualsVerifier library.

Let’s add the EqualsVerifier Maven test dependency:

Let’s verify that our Team class follows the equals() and hashCode() contracts:

It’s worth noting that EqualsVerifier tests both the equals() and hashCode() methods.

EqualsVerifier is much stricter than the Java SE contract. For example, it makes sure that our methods can’t throw a NullPointerException. Also, it enforces that both methods, or the class itself, is final.

It’s important to realize that the default configuration of EqualsVerifier allows only immutable fields. This is a stricter check than what the Java SE contract allows. This adheres to a recommendation of Domain-Driven Design to make value objects immutable.

If we find some of the built-in constraints unnecessary, we can add a suppress(Warning.SPECIFIC_WARNING) to our EqualsVerifier call.

5.4 Сравнение двух объектов Person

Что мы получили в итоге? Если мы дошли до конца метода, значит, у нас объект типа и ссылка не . Тогда преобразовываем его к типу и будем сравнивать внутренности обоих объектов. Это и есть наш сценарий номер 4.

Код Описание
Сравниваем ссылки

Переданный объект — ?

Если переданный объект не типа

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

А как сравнивать два объекта ? Они равны, если у них равны имена () и возраст (). Итоговый код будет выглядеть так:

Код Описание
Сравниваем ссылки

Переданный объект — ?

Если переданный объект не типа

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

Но и это еще не все.

Во-первых, поле name имеет тип , а значит, поля name нужно сравнивать с помощью вызова метода equals.

Во-вторых, поле name вполне себе может быть равным : тогда вызвать метод у него нельзя. Нужна дополнительная проверка на null:

Однако если name равно в обоих объектах , имена все-таки равны.

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

Если возрасты не равны, сразу

Если равно , нет смысла сравнивать через . Тут либо второе поле name равно , либо нет.

Сравниваем два поля name через .

5.5 Метод hashCode()

Кроме метода , который выполняет детальное сравнение всех полей обоих объектов, есть еще один метод, который может использоваться для неточного, но очень быстрого сравнения — .

Представьте, что вы сортируете в алфавитном порядке список из тысяч слов, и вам нужно постоянно попарно сравнивать слова. А слова длинные, и букв в них много. В общем, такое сравнение будет идти очень долго.

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

Метод работает по похожему принципу. Если его вызвать у объекта, то он вернет некое число — аналог первой буквы в слове. Это число обладает такими свойствами:

  • У одинаковых объектов всегда одинаковые hash-code
  • У разных объектов могут быть одинаковые hash-code, а могут быть разные
  • Если у объектов разные hash-code, объекты точно разные

Для большего понимания перепишем эти свойства относительно слов:

  • У одинаковых слов всегда одинаковые первые буквы
  • У разных слов могут быть одинаковые первые буквы, а могут быть и разные
  • Если у слов разные первые буквы, слова точно разные

Последнее свойство и используется для ускоренного сравнения объектов:

Сначала у двух объектов вычисляются hash-code. Если эти hash-code разные, то объекты точно разные, и сравнивать их дальше не нужно.

А вот если hash-code одинаковые, придется все же сравнивать объекты с помощью equals.

equals()

The Object class defines both the equals() and hashCode() methods – which means that these two methods are implicitly defined in every Java class, including the ones we create:

We would expect income.equals(expenses) to return true. But with the Money class in its current form, it won’t.

The default implementation of equals() in the class Object says that equality is the same as object identity. And income and expenses are two distinct instances.

2.1. Overriding equals()

Let’s override the equals() method so that it doesn’t consider only object identity, but rather also the value of the two relevant properties:

2.2. equals() Contract

Java SE defines a contract that our implementation of the equals() method must fulfill. Most of the criteria are common sense. The equals() method must be:

  • reflexive: an object must equal itself
  • symmetric: x.equals(y) must return the same result as y.equals(x)
  • transitive: if x.equals(y) and y.equals(z) then also x.equals(z)
  • consistent: the value of equals() should change only if a property that is contained in equals() changes (no randomness allowed)

We can look up the exact criteria in the Java SE Docs for the Object class.

2.3. Violating equals() Symmetry With Inheritance


If the criteria for equals() is such common sense, how can we violate it at all? Well, violations happen most often, if we extend a class that has overridden equals(). Let’s consider a Voucher class that extends our Money class:

At first glance, the Voucher class and its override for equals() seem to be correct. And both equals() methods behave correctly as long as we compare Money to Money or Voucher to Voucher. But what happens, if we compare these two objects?

That violates the symmetry criteria of the equals() contract.

2.4. Fixing equals() Symmetry With Composition

To avoid this pitfall, we should favor composition over inheritance.

Instead of subclassing Money, let’s create a Voucher class with a Money property:

And now, equals will work symmetrically as the contract requires.

Методы System.Object

Ниже перечислены все методы данного класса:

ToString() Метод ToString() возвращает символьную строку, содержащую описание того объекта, для которого он вызывается. Кроме того, метод ToString() автоматически вызывается при выводе содержимого объекта с помощью метода WriteLine(). Этот метод переопределяется во многих классах, что позволяет приспосабливать описание к конкретным типам объектов, создаваемых в этих классах. Применяйте этот метод, когда нужно получить представление о содержимом объекта — возможно, в целях отладки. Он предлагает очень ограниченные средства форматирования данных. Например, даты в принципе могут быть отображены в огромном разнообразии форматов, но DateTime.ToString() не оставляет никакого выбора в этом отношении

Если нужно более сложное строковое представление, которое, например, принимает во внимание установленные предпочтения или местные стандарты, то понадобится реализовать интерфейс IFormattable. GetHashCode() Этот метод используется, когда объект помещается в структуру данных, известную как карта (map), которая также называется хеш-таблицей или словарем

Применяется классами, которые манипулируют этими структурами, чтобы определить, куда именно в структуру должен быть помещен объект. Если вы намерены использовать свой класс как ключ словаря, то должны переопределить GetHashCode(). Существуют достаточно строгие требования относительно того, как нужно реализовывать перегрузку. Хеш-код можно использовать в любом алгоритме, где хеширование применяется в качестве средства доступа к хранимым объектам. Следует, однако, иметь в виду, что стандартная реализация метода GetHashCode() не пригодна на все случаи применения.

Equals() и ReferenceEquals() По умолчанию метод Equals (object) определяет, ссылается ли вызывающий объект на тот же самый объект, что и объект, указываемый в качестве аргумента этого метода, т.е. он определяет, являются ли обе ссылки одинаковыми. Метод Equals (object) возвращает логическое значение true, если сравниваемые объекты одинаковы, в противном случае — логическое значение false. Он может быть также переопределен в создаваемых классах. Это позволяет выяснить, что же означает равенство объектов для создаваемого класса. Например, метод Equals (object) можно определить таким образом, чтобы в нем сравнивалось содержимое двух объектов. Как несложно догадаться, учитывая существование трех различных методов сравнения объектов, среда .NET использует довольно сложную схему определения эквивалентности объектов. Следует учитывать и использовать тонкие различия между этими тремя методами и операцией сравнения ==. Кроме того, также существуют ограничения, регламентирующие, как следует переопределять виртуальную версию Equals() с одним параметром, если вы решитесь на это — поскольку некоторые базовые классы из пространства имен System.Collections вызывают этот метод и ожидают от него определенного поведения.

Finalize() Назначение этого метода в C# примерно соответствует деструкторам С++, и он вызывается при сборке мусора для очистки ресурсов, занятых ссылочным объектом. Реализация Finalize() из Object на самом деле ничего не делает и игнорируется сборщиком мусора. Обычно переопределять Finalize() необходимо, если объект владеет неуправляемыми ресурсами, которые нужно освободить при его уничтожении. Сборщик мусора не может сделать это напрямую, потому что он знает только об управляемых ресурсах, поэтому полагается на финализацию, определенную вами. GetType() Этот метод возвращает экземпляр класса, унаследованный от System.Type. Этот объект может предоставить большой объем информации о классе, членом которого является ваш объект, включая базовый тип, методы, свойства и т.п. System.Type также представляет собой стартовую точку технологии рефлексии .NET. Clone() Этот метод создает копию объекта и возвращает ссылку на эту копию (а в случае типа значения — ссылку на упаковку). Отметим, что при этом выполняется неглубокое копирование, т.е. копируются все типы значений в классе. Если же класс включает в себя члены ссылочных типов, то копируются только ссылки, а не объекты, на которые они указывают. Этот метод является защищенным, а потому не может вызываться для копирования внешних объектов. К тому же он не виртуальный, а потому переопределять его реализацию нельзя.

Давайте рассмотрим применение некоторых из этих методов на конкретном примере:

Переопределение метода equals в Java

Когда же стоит переопределять метод equals? Это стоит делать только тогда, когда для вашего класса определено понятие логической эквивалентности, которая не совпадает с тождественностью объектов.

Например, для классов Integer и String данное понятие можно применить.

Переопределение метода equals нужно не только для того, чтобы удовлетворить ожидания программистов, оно также позволяет использовать экземпляры класса в качестве ключей в некой схеме или элементов в неком наборе, имеющих необходимое и предсказуемое поведение. Что это значит?

Давайте рассмотрим пример кода:

Java

public class Address { private Integer number; private String street;

public Address(Integer number, String street) { this.number = number; this.street = street; }

public Integer getNumber() { return number; }

public String getStreet() { return street; }

public static void main(String[] args) { List<Address> addressList = new ArrayList<>(); addressList.add(new Address(1, «Test street»)); addressList.add(new Address(1, «Test street»)); while (addressList.remove(new Address(1, «Test street»))); System.out.println(addressList.size()); } }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

publicclassAddress{

privateIntegernumber;

privateStringstreet;

publicAddress(Integernumber,Stringstreet){

this.number=number;

this.street=street;

}

publicIntegergetNumber(){

returnnumber;

}

publicStringgetStreet(){

returnstreet;

}

publicstaticvoidmain(Stringargs){

List<Address>addressList=newArrayList<>();

addressList.add(newAddress(1,»Test street»));

addressList.add(newAddress(1,»Test street»));

while(addressList.remove(newAddress(1,»Test street»)));

System.out.println(addressList.size());

}

}

Мы создаем список объектов Address и добавляем объект класса Address с одинаковым конструктором. Добавляем данный объект 2 раза в List, и с помощью цикла удаляем его. Затем выводим в консоль длину списка.

После выполнения программы, в консоли отобразится число 2. Но почему же количество записей в списке не изменилось? Ведь мы пытались их удалить. В примере объекты с равными полями number и street, и всё-таки – почему они не удалились?

При удалении объектов из списка неявно вызывается метод equals и если объект который мы передаем для удаления содержится в списке, то он удаляется. Реализация equals в классе Object проверяет только равенство ссылок. А ссылки у экземпляров классов различные, потому что это совершенно разные объекты, каждый их них был создан с помощью оператора new. Как же это исправить? Необходимо переопределить метод equals в классе Address:

Java

@Override public boolean equals(Object o) { if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

Address address = (Address) o;

if (number != null ? !number.equals(address.number) : address.number != null) return false;

if (street != null ? !street.equals(address.street) : address.street != null) return false;

return true; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

@Override

publicbooleanequals(Objecto){

if(this==o)returntrue;

if(o==null||getClass()!=o.getClass())returnfalse;

Address address=(Address)o;

if(number!=null?!number.equals(address.number)address.number!=null)

returnfalse;

if(street!=null?!street.equals(address.street)address.street!=null)

returnfalse;

returntrue;

}

Запустим программу снова и увидим, что количество объектов в списке стало равным нулю.

Скачать исходник программы из этого урока, вы можете, нажав на кнопку ниже:

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

Правильная реализация hashCode()

Чтобы избежать проблем нам необходимо корректно переопределять hashCode(). Рассмотрим два варианта.

  1. Использовать метод hash класса Objects. Для класса Person это будет выглядеть следующим образом :

@Override public int hashCode() { return Objects.hash(name, phone); }

1 2 3 4

@Override

publicinthashCode(){

returnObjects.hash(name,phone);

}

Прекрасная реализация в одну строчку. Из минусов – не подойдет вам, в случаем высоких требований к производительности приложения. В этом случае используйте второй способ


2. Напишите свою реализацию, используя следующий алгоритм:

  1. Создайте переменную result и положите в нее значение hashCode() первого значимого поля класса. Если поле примитив, то используйте вызов Type.hashCode(value), если экземпляр класса, то рекурсивно вычисляйте hashCode() для полей класса, либо используйте уже готовый метод hashCode() этого класса. Если поле массив, то рекурсивно вычисляйте hashCode() каждого элемента.
  2. Вычислите hashCode() каждого значимого поля и скомбинируйте следующим образом : result = 31 * result + Type.hashCode(value)
  3. Верните результат

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

@Override public int hashCode() { int result = name.hashCode(); result = 31 * result + Integer.hashCode(phone);

return result; }

1 2 3 4 5 6 7

@Override

publicinthashCode(){

intresult=name.hashCode();

result=31*result+Integer.hashCode(phone);

returnresult;

}

Вы можете придумать свою собственную реализацию hashCode(), главное, помните о его контракте!

Overloads

Позволяет определить, совпадает ли базовый системный тип текущего объекта Type с базовым системным типом указанного объекта Type.Determines if the underlying system type of the current Type is the same as the underlying system type of the specified Type.

Определяет, совпадает ли базовый системный тип текущего объекта Type с базовым системным типом указанного объекта Object.Determines if the underlying system type of the current Type object is the same as the underlying system type of the specified Object.

Equals(Type)

Позволяет определить, совпадает ли базовый системный тип текущего объекта Type с базовым системным типом указанного объекта Type.Determines if the underlying system type of the current Type is the same as the underlying system type of the specified Type.

Parameters

o
Type

Объект, базовый системный тип которого сравнивается с базовым системным типом текущего типа Type.The object whose underlying system type is to be compared with the underlying system type of the current Type.

Returns

Boolean

Значение , если базовый системный тип параметра совпадает с базовым системным типом текущего объекта Type; в противном случае — значение . if the underlying system type of is the same as the underlying system type of the current Type; otherwise, .

Implements

Examples

В следующем примере для сравнения двух типов используется .The following example uses to compare two types.

UnderlyingSystemType

Equals(Object)

Определяет, совпадает ли базовый системный тип текущего объекта Type с базовым системным типом указанного объекта Object.Determines if the underlying system type of the current Type object is the same as the underlying system type of the specified Object.

Parameters

o
Object

Объект, базовый системный тип которого сравнивается с базовым системным типом текущего типа Type.The object whose underlying system type is to be compared with the underlying system type of the current Type. Для успешного сравнения необходимо, чтобы параметр можно было привести к объекту типа Type или преобразовать в объект такого типа.For the comparison to succeed, must be able to be cast or converted to an object of type Type.

Returns

Boolean

Значение , если базовый системный тип параметра совпадает с базовым системным типом текущего объекта Type; в противном случае — значение . if the underlying system type of is the same as the underlying system type of the current Type; otherwise, . Этот метод также возвращает в следующих случаях:This method also returns if:

o имеет значение null.

is .

невозможно привести к объекту или преобразовать в объект Type.

cannot be cast or converted to a Type object.

Implements

Examples

В следующем примере используется для сравнения различных экземпляров объектов Type с различными экземплярами Object.The following example uses to compare various Type object instances with various Object instances.

В примере следует обратить внимание на две вещи:Two things are particularly worth noting about the example:

  • Сравнение объекта Type, представляющего целое число с TypeInfoным объектом, представляющим целочисленное значение, возвращаемое , так как TypeInfo является производной от Type.The comparison of a Type object that represents an integer with a TypeInfo object that represents an integer return because TypeInfo is derived from Type.

  • Сравнение объекта Type, представляющего объект IList<T> (открытый универсальный тип) с ным объектом (закрытым универсальным типом), возвращает .The comparison of a Type object that represents a IList<T> object (an open generic type) with a object (a closed generic type) returns .

Remarks

Этот метод переопределяет метод Object.Equals.This method overrides Object.Equals. Он приводит к объекту типа Type и вызывает метод .It casts to an object of type Type and calls the method.

UnderlyingSystemType

7.2 Метод Arrays.toString()

И первый метод, который мы рассмотрим, будет называться . Но для начала немного предыстории.

У каждого массива в Java есть метод , который возвращает «текстовое представление массива». Получить текстовое представление массива можно с помощью такой конструкции:

Где — это имя переменной-массива, а — это имя переменной, в которую сохранится строковое представление массива.

Но если вы попробуете вывести массив на экран с помощью метода , скорее всего увидите что-то типа:

Первая буква означает, что это массив типа , а символы после – адрес массива в памяти. С одной стороны, переменная-массив именно эту информацию и хранит, а с другой — мы ожидали иное, правда?

Хотелось бы увидеть значения, которые есть в массиве! И вот для того, чтобы увидеть именно значения массива, и придумали метод . Его вызов выглядит так:

Примеры:

Переменная будет содержать строковое значение
Переменная будет содержать строковое значение
Переменная будет содержать строковое значение

Что произошло? Понимание equals() и hashCode()

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

Во втором сравнении для переменной был переопределён метод . Для обоих объектов имя равно «Homer», но для метод возвращает другое значение. В этом случае результат метода будет , так как в нём содержится сравнение с хэш-кодом.

Вы, должно быть, поняли, что в коллекции будет три объекта . Давайте разберём это.

Первый объект в наборе будет вставлен как обычно:

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

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

Как мы знаем, объект использует другое значение хэш-кода в отличие от обычного экземпляра . По этой причине этот элемент будет вставлен в коллекцию:


С этим читают