Какие проблемы / ловушки необходимо учитывать при переопределении equals
и hashCode
?
источник
Какие проблемы / ловушки необходимо учитывать при переопределении equals
и hashCode
?
equals()
( javadoc ) должен определять отношение эквивалентности (оно должно быть рефлексивным , симметричным и транзитивным ). Кроме того, он должен быть согласованным (если объекты не изменены, он должен продолжать возвращать одно и то же значение). Кроме того, o.equals(null)
всегда должен возвращать false.
hashCode()
( javadoc ) также должен быть согласованным (если объект не изменен в терминах equals()
, он должен продолжать возвращать одно и то же значение).
Соотношение между этими двумя методами:
Когда бы
a.equals(b)
то ни было , тогдаa.hashCode()
должно быть так же, какb.hashCode()
.
Если вы переопределяете одно, то вы должны переопределить другое.
Используйте тот же набор полей, который вы используете для вычисления equals()
для вычисления hashCode()
.
Используйте отличные вспомогательные классы EqualsBuilder и HashCodeBuilder из библиотеки Apache Commons Lang . Пример:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
При использовании коллекции или карты на основе хеша, таких как HashSet , LinkedHashSet , HashMap , Hashtable или WeakHashMap , убедитесь, что hashCode () ключевых объектов, которые вы помещаете в коллекцию, никогда не изменяется, пока объект находится в коллекции. Пуленепробиваемый способ обеспечить это - сделать ваши ключи неизменяемыми, что имеет и другие преимущества .
instanceof
возвращается false, если ее первый операнд имеет значение null (снова эффективная Java).Есть некоторые проблемы, на которые стоит обратить внимание, если вы имеете дело с классами, которые сохраняются с использованием Object-Relationship Mapper (ORM), например Hibernate, если вы не думали, что это уже было неоправданно сложно!
Ленивые загруженные объекты являются подклассами
Если ваши объекты сохраняются с использованием ORM, во многих случаях вы будете иметь дело с динамическими прокси, чтобы избежать слишком ранней загрузки объекта из хранилища данных. Эти прокси реализованы как подклассы вашего собственного класса. Это значит, что
this.getClass() == o.getClass()
вернетсяfalse
. Например:Если вы имеете дело с ORM, использование
o instanceof Person
- единственное, что будет вести себя правильно.Ленивые загруженные объекты имеют нулевые поля
ORM обычно используют геттеры для принудительной загрузки лениво загруженных объектов. Это означает, что
person.name
будет,null
еслиperson
загружается ленивый, даже еслиperson.getName()
принудительно загружается и возвращает «Джон Доу». По моему опыту, это всплывает чаще вhashCode()
иequals()
.Если вы имеете дело с ORM, всегда используйте геттеры и никогда не указывайте ссылки в
hashCode()
иequals()
.Сохранение объекта изменит его состояние
Постоянные объекты часто используют
id
поле для хранения ключа объекта. Это поле будет автоматически обновляться при первом сохранении объекта. Не используйте поле id вhashCode()
. Но вы можете использовать его вequals()
.Шаблон, который я часто использую,
Но: вы не можете включить
getId()
вhashCode()
. Если вы делаете, когда объект сохраняется, егоhashCode
изменения. Если объект находится вHashSet
, вы никогда не найдете его снова.В моем
Person
примере я, вероятно, использовал быgetName()
дляhashCode
иgetId()
плюсgetName()
(только для паранойи) дляequals()
. Это нормально, если есть некоторый риск «столкновений»hashCode()
, но никогда не подходит дляequals()
.hashCode()
следует использовать неизменяемый набор свойств изequals()
источник
Saving an object will change it's state
!hashCode
должен вернутьсяint
, так как вы будете использоватьgetName()
? Можете ли вы привести пример для вашегоhashCode
Разъяснение по поводу
obj.getClass() != getClass()
.Это утверждение является результатом
equals()
недружественного наследования. JLS (спецификация языка Java) указывает, что ifA.equals(B) == true
thenB.equals(A)
также должен возвращатьсяtrue
. Если вы пропустите этот оператор, наследующий классы, которые переопределяютequals()
(и изменяют его поведение), это нарушит эту спецификацию.Рассмотрим следующий пример того, что происходит, когда оператор опущен:
Выполнение
new A(1).equals(new A(1))
Кроме того ,new B(1,1).equals(new B(1,1))
результат выдает так, как это должно быть.Это выглядит очень хорошо, но посмотрите, что произойдет, если мы попробуем использовать оба класса:
Очевидно, это неправильно.
Если вы хотите обеспечить симметричное условие. a = b, если b = a, и принцип подстановки Лискова вызывает
super.equals(other)
не только в случаеB
экземпляра, но и проверяет после,A
например:Который будет выводить:
Где, если
a
это не ссылка наB
, то это может быть ссылка на классA
(потому что вы расширяете его), в этом случае выsuper.equals()
тоже вызываете .источник
ThingWithOptionSetA
может быть равным приThing
условии, что все дополнительные параметры имеют значения по умолчанию, и аналогично для aThingWithOptionSetB
, то должна быть возможностьThingWithOptionSetA
сравнения a равным a,ThingWithOptionSetB
только если все неосновные свойства обоих объектов соответствуют их значениям по умолчанию, но Я не вижу, как вы проверяете это.B b2 = new B(1,99)
, тоb.equals(a) == true
иa.equals(b2) == true
ноb.equals(b2) == false
.Для реализации, дружественной к наследованию, ознакомьтесь с решением Тэла Коэна « Как правильно реализовать метод equals ()»?
Резюме:
В своей книге « Эффективное руководство по языку программирования Java» (Addison-Wesley, 2001) Джошуа Блох утверждает, что «просто нет способа расширить экземплярный класс и добавить аспект при сохранении контракта равных». Тал не согласен.
Его решение состоит в том, чтобы реализовать equals (), вызывая другой несимметричный blindlyEquals () в обоих направлениях. blindlyEquals () переопределяется подклассами, equals () наследуется и никогда не переопределяется.
Пример:
Обратите внимание, что метод equals () должен работать в иерархиях наследования, если необходимо выполнить принцип подстановки Лискова .
источник
if (this.getClass() != o.getClass()) return false
, но гибко в том смысле, что возвращает ложь только в том случае, если производный класс (ы) пытается изменить значение equals. Это правильно?Все еще поражен тем, что никто не рекомендовал библиотеку гуавы для этого.
источник
this
вthis.getDate()
средстве ничего (кроме беспорядка)if (!(otherObject instanceof DateAndPattern)) {
. Согласитесь с Эрнаном и Стивом Куо (хотя это вопрос личных предпочтений), но, тем не менее, +1.В суперклассе есть два метода как java.lang.Object. Нам нужно переопределить их для пользовательского объекта.
Равные объекты должны генерировать один и тот же хэш-код, если они равны, однако неравные объекты не должны создавать различные хэш-коды.
Если вы хотите получить больше, проверьте эту ссылку как http://www.javaranch.com/journal/2002/10/equalhash.html
Это еще один пример, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
Радоваться, веселиться! @. @
источник
Есть несколько способов выполнить проверку на равенство классов перед проверкой равенства членов, и я думаю, что оба полезны в правильных обстоятельствах.
instanceof
оператора.this.getClass().equals(that.getClass())
.Я использую # 1 в
final
реализации equals или при реализации интерфейса, который предписывает алгоритм для equals (например,java.util
интерфейсы коллекций - правильный способ проверки с(obj instanceof Set)
любым интерфейсом, который вы реализуете). Обычно это плохой выбор, когда равенства могут быть переопределены, потому что это нарушает свойство симметрии.Вариант № 2 позволяет безопасно расширять класс, не перекрывая равные и не нарушая симметрию.
Если ваш класс также
Comparable
, какequals
иcompareTo
методы должны соответствовать слишком. Вот шаблон для метода equals вComparable
классе:источник
final
, иcompareTo()
метод был переопределен для изменения порядка сортировки, экземпляры подкласса и суперкласса не должны рассматриваться как равные. Когда эти объекты использовались вместе в дереве, ключи, которые были «равны» в соответствии сinstanceof
реализацией, могли бы быть недоступны для поиска.Для равных, заглянуть в секреты Равных по Angelika Langer . Я люблю это очень сильно. Она также является отличным FAQ по Generics в Java . Посмотрите ее другие статьи здесь (прокрутите вниз до «Базовая Java»), где она также продолжает Часть 2 и «Сравнение смешанных типов». Приятного чтения!
источник
Метод equals () используется для определения равенства двух объектов.
так как int значение 10 всегда равно 10. Но этот метод equals () касается равенства двух объектов. Когда мы говорим объект, он будет иметь свойства. Чтобы решить вопрос равенства, рассматриваются эти свойства. Нет необходимости, чтобы все свойства учитывались для определения равенства, и в отношении определения класса и контекста можно принять решение. Тогда метод equals () можно переопределить.
мы должны всегда переопределять метод hashCode () всякий раз, когда переопределяем метод equals (). Если нет, что будет? Если мы используем хеш-таблицы в нашем приложении, оно будет работать не так, как ожидалось. Поскольку hashCode используется для определения равенства хранимых значений, он не будет возвращать правильное соответствующее значение для ключа.
В качестве реализации по умолчанию используется метод hashCode () класса Object, который использует внутренний адрес объекта, преобразует его в целое число и возвращает его.
Пример вывода кода:
источник
По логике мы имеем:
a.getClass().equals(b.getClass()) && a.equals(b)
⇒a.hashCode() == b.hashCode()
Но не наоборот!
источник
Я обнаружил одну ошибку: два объекта содержат ссылки друг на друга (один пример - отношения родитель / потомок с удобным методом для родителя, чтобы получить всех детей).
Такого рода вещи довольно распространены, например, при отображении Hibernate.
Если вы включите оба конца отношения в свой hashCode или в тесты equals, можно попасть в рекурсивный цикл, который заканчивается в исключении StackOverflowException.
Самое простое решение - не включать коллекцию getChildren в методы.
источник
equals()
. Если бы сумасшедший ученый создал меня дубликат, мы были бы эквивалентны. Но у нас не было бы того же отца.