Java equals переопределение пример

Методы .equals и .hashcode в Java. Отличия реализации по умолчанию от реализации на практике

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

В Java так устроено, что любой класс, который вы определяете, наследуется от класса Object. Таким образом класс Object является суперклассом любого класса в любой программе.

Это означает, что абсолютно любой класс содержит методы, которые определены в классе Object. Методы .equals() и .hashcode() — одни из них.

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

1). Если x.equals(y) == true, то обязательно hashcode(x) == hashcode(y)

2) Если hashcode(x) == hashcode(y), то не обязательно x.equals(y) == true

Метод .equals()

Отношение эквивалентности (алгебра)

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

Отношение эквивалентности — это бинарное (бинарное — значит между двумя) отношение, которое является:

Читайте также:  Python get local path

Таким образом, если на множестве определено отношение эквивалентности, множество можно разделить на подмножества — классы эквивалентности.

Каждый класс эквивалентности содержит внутри себя только те элементы, которые эквиваленты (более формально — находятся в отношении эквивалентности) между собой.

Реализация .equals() по умолчанию

Метод .equals() в классе Object реализован примерно следующим образом:

public boolean equals(Object x)

Фактически он делает следующее: Он принимает в качестве аргумента ссылочную переменную и проверяет, ссылается ли они на тот же объект (ту же область памяти, если быть точнее), что и объект, к которому мы применили метод .equals().

Таким образом, стандартная реализация .equals() выстраивает отношение эквивалентности, которое можно описать так: две ссылки эквивалентны, если они ссылаются на одну и ту же область памяти.

Такая реализация не противоречит математической идеологии, описанной выше. Однако на практике метод .equals() часто переопределяют в подклассах.

Как и зачем переопределяют метод .equals()?

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

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

Конкретную кодовую реализацию я приводить не буду, потому что она не так важна, как сама идея

Это и другие возможные переопределения метода .equals() мало того, что расширяют круг наших возможностей, так ещё и не лишают старых, ведь мы по прежнему имеем возможность проверять, ссылаются ли две ссылки на одну область памяти, используя операнд ==, вместо прежнего .equals()

Метод .hashcode()

Сюръекция (алгебра)

Сюръекция — сопоставление элементам множества X элементов второго множества Y, при котором для любого элемента из Y есть хотя-бы один сопоставленный элемент из X.

Если немного более подробно разобрать это определение, то мы увидим следующее:

  • Даже несколько элементов из X могут быть сопоставлены одному и тому же элементу из Y (это называется коллизией).
  • Возможно есть такое элемент из X, и даже возможно не один, что он не сопоставлен никакому элементу из Y. (см. рисунок, всё интуитивно)

Что происходит в java?

Метод .hashcode() как-раз осуществляет сюръекцию. Множеством X выступает множество всевозможных объектов которые мы можем создать, множеством Y выступает область значений типа данных int. Метод .hashcode() вычисляет каким-то скрытым от нас способом целое число, опираясь на объект, к которому применяется.

Единственное отличие метода .hashcode() от сюръекции в том, что любой объект может быть обработан методом .hashcode()

Здесь нет элементов по типу E из пред. рисунка

Реализация .hashcode() по умолчанию?

Насколько я понял, точно так никто в этом и не разобрался. Есть много версий:

  • Значение .hashcode() — это область памяти, где лежит объект
  • Значение .hashcode() — это число, создаваемое генератором случайных чисел в какой-то момент
  • Сама функция написана не на Java а вообще на C.

И многие другие. В общем каким-то образом она всё же устроена, но самое главное в том, что стандартная реализация .hashcode() со стандартной реализацией .equals() подчиняются правилу, приведённому в самом начале статьи

Как и зачем переопределяют метод .hashcode()?

Основной причиной для изменения метода .hashcode() является то, что желают изменить .equals(), однако смена стандартной реализации .equals() приводит к нарушению правила из начала статьи

Второстепенной причиной для изменения метода .hashcode() является то, что желают изменить вероятность коллизии (эта причина встречается реже)

Источник

Перегрузка методов equals() и hashCode() в Java

Java-университет

Переопределение методов equals() и hashCode() в Java

Перегрузка методов equals() и hashCode() в Java - 1

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 предлагает следующие правила для переопределения этих методов:

  1. Рефлексивность: Объект должен равняться себе самому.
  2. Симметричность: если a.equals(b) возвращает true , то b.equals(a) должен тоже вернуть true .
  3. Транзитивность: если a.equals(b) возвращает true и b.equals(c) тоже возвращает true , то c.equals(a) тоже должен возвращать true .
  4. Согласованность: повторный вызов метода equals() должен возвращать одно и тоже значение до тех пор, пока какое-либо значение свойств объекта не будет изменено. То есть, если два объекта равны в Java, то они будут равны пока их свойства остаются неизменными.
  5. Сравнение null : объект должны быть проверен на null . Если объект равен null , то метод должен вернуть false , а не NullPointerException . Например, a.equals(null) должен вернуть false .

Соглашение между equals и hashCode в Java

  1. Если объекты равны по результатам выполнения метода equals , тогда их hashcode должны быть одинаковыми.
  2. Если объекты не равны по результатам выполнения метода equals , тогда их hashcode могут быть как одинаковыми, так и разными. Однако для повышения производительности, лучше, чтобы разные объекты возвращали разные коды.

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

 @Override public boolean equals(Object obj) < /*1. Проверьте*/if (obj == this) < /*и верните */ return true; > 
 if (obj == null || obj.getClass() != this.getClass())
 Person guest = (Person) obj; return 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 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

  1. Вместо того, чтобы переопределять метод equals (Override) программист перегружает его (Overload) Синтаксис метода equals() в классе Object определен как public boolean equals(Object obj) , но многие программисты ненароком перегружают метод: public boolean equals(Person obj) — вместо Object в качестве аргумента используют имя своего класса (напр. Person). Эту ошибку сложно обнаружить из-за static binding . Таким образом, если вы вызовете этот метод для объекта своего класса, то метод не просто скомпилируется, а даже сделает это корректно. Однако, если вы положите ваш объект в коллекцию, например ArrayList и вызовете метод contains() , работа которого основана на методе equals() , то метод contains не сможет обнаружить ваш объект.
  2. При переопределении метода equals() не проверять на null переменные, что в конечном итоге заканчивается NullPointerException при вызове equals() . Ниже представлен корректный код.
 firstname == guest.firstname || (firstname != null && firstname.equals(guest.firstname)); 

Подсказки как писать в Java метод equals

  1. Большинство IDE такие как NetBeans, Eclipse и IntelliJ IDEA обеспечивают поддержку генерации методов equals() и hashCode() . В Eclipse нажмите правую кнопку -> source -> generate equals() и hashCode() .
  2. Если в классе есть уникальный бизнес-ключ, то будет достаточно сделать проверку только на равенство этих полей. Как в нашем примере “id” — уникальный номер для каждого Person.
  3. При переопределении hashCode() в Java удостоверьтесь в использовании всех полей, что были использованы в методе equals() .
  4. String и классы-оболочки такие как Integer , Float и Double переопределяют метод equals() , но StringBuffer не переопределяет.
  5. При любой возможности делайте поля immutable используя final переменные в Java.
  6. При сравнении String объектов используйте equals() вместо оператора == .
  7. Два объекта которые логически равны, но загружены из разных ClassLoader не могут быть равными. Помните, что проверка с помощью getClass() вернет false если класс-загрузчик разный.
  8. Используйте @Override аннотацию также для метода hashCode , так как это предупреждает неуловимые ошибки, например возвращаемое значение метода int , однако некоторые программисты возвращают long .

Источник

Оцените статью