Эта проблема наиболее очевидна, когда у вас есть разные реализации интерфейса, и для целей конкретной коллекции вы заботитесь только о представлении объектов на уровне интерфейса. Например, предположим, что у вас был такой интерфейс:
public interface Person {
int getId();
}
Обычный способ реализации hashcode()
и equals()
реализации классов должен иметь такой код в equals
методе:
if (getClass() != other.getClass()) {
return false;
}
Это вызывает проблемы, когда вы смешиваете реализации Person
в HashMap
. Если HashMap
только заботится о представлении на уровне интерфейса Person
, то это может привести к дубликатам, которые отличаются только своими классами реализации.
Вы могли бы заставить этот случай работать, используя один и тот же либеральный equals()
метод для всех реализаций, но тогда вы рискуете equals()
сделать неправильную вещь в другом контексте (например, сравнить два Person
s, которые поддерживаются записями базы данных с номерами версий).
Моя интуиция говорит мне, что равенство должно быть определено для каждой коллекции, а не для класса. При использовании коллекций, основанных на упорядочении, вы можете использовать обычай, Comparator
чтобы выбрать правильное упорядочение в каждом контексте. Аналогов для коллекций на основе хеша не существует. Почему это?
Просто чтобы прояснить, этот вопрос отличается от « Почему .compareTo () в интерфейсе, а .equals () в классе в Java? », Потому что он касается реализации коллекций. compareTo()
и equals()
/ hashcode()
оба страдают от проблемы универсальности при использовании коллекций: вы не можете выбирать разные функции сравнения для разных коллекций. Таким образом, для целей этого вопроса иерархия наследования объекта не имеет значения вообще; имеет значение только то, определена ли функция сравнения для каждого объекта или для каждой коллекции.
источник
Person
реализации ожидаемогоequals
иhashCode
поведения. Вы бы тогда имелиHashMap<PersonWrapper, V>
. Это один пример, когда подход чистого ООП не элегантен: не каждая операция над объектом имеет смысл как метод этого объекта. Вся ЯвыObject
типа представляет собой совокупность различных обязанностей - толькоgetClass
,finalize
иtoString
методы кажутся оправданными удаленно с помощью современных передовых методов.IEqualityComparer<T>
коллекцию на основе хеша. Если вы не укажете один, он использует реализацию по умолчанию на основеObject.Equals
иObject.GetHashCode()
. 2) Переопределение IMOEquals
для изменяемого ссылочного типа редко является хорошей идеей. Таким образом, равенство по умолчанию является довольно строгим, но вы можете использовать более мягкое правило равенства, когда вам это нужно с помощью обычаяIEqualityComparer<T>
.Ответы:
Этот дизайн иногда называют «универсальным равенством», это убеждение, что две вещи равны или нет, является универсальным свойством.
Более того, равенство является свойством двух объектов, но в ОО вы всегда вызываете метод для одного отдельного объекта , и этот объект получает единоличное решение о том, как обрабатывать этот вызов метода. Таким образом, в проекте, подобном Java, где равенство является свойством одного из двух сравниваемых объектов, даже невозможно гарантировать некоторые базовые свойства равенства, такие как симметрия (
a == b
⇔b == a
), поскольку в первом случае метод вызывается,a
а во втором случае вызывается,b
и в силу основных принципов ОО, это исключительноa
решение (в первом случае) илиb
Решение (во втором случае), считает ли он себя равным другому. Единственный способ получить симметрию - это заставить два объекта взаимодействовать, но если они этого не сделают ... не повезло.Одним из решений было бы сделать равенство не свойством одного объекта, а либо свойством двух объектов, либо свойством третьего объекта. Этот последний вариант также решает проблему универсального равенства, потому что, если вы сделаете равенство свойством третьего объекта «контекста», тогда вы можете представить себе разные
EqualityComparer
объекты для разных контекстов.Это является конструкция выбрана для Haskell, например, с
Eq
классом типов. Это также дизайн, выбранный некоторыми сторонними библиотеками Scala (например, ScalaZ), но не ядро Scala или стандартная библиотека, которая использует универсальное равенство для совместимости с базовой платформой хоста.Это интересно, и дизайн выбран с Явой
Comparable
/Comparator
интерфейсами. Разработчики Java ясно знали о проблеме, но по какой-то причине решили ее только для упорядочивания, но не для равенства (или хеширования).Итак, что касается вопроса
ответ "я не знаю". Очевидно, что разработчики Java знали об этой проблеме, о чем свидетельствует существование
Comparator
, но они явно не считали ее проблемой равенства и хеширования. Другие языки и библиотеки делают другой выбор.источник
The methods equals and hashCode are declared for the benefit of hashtables such as java.util.Hashtable
т. е. обаequals
иhashCode
были введеныObject
Java-разработчиками в качестве методов исключительно радиHashtable
- в спецификации нет никакого понятия UE или чего-либо другого, и цитата для меня достаточно ясна; если бы неHashtable
,equals
вероятно, был бы в интерфейсе, какComparable
. Таким образом, если раньше я считал ваш ответ правильным, то теперь считаю его необоснованным.clone
- изначально это был оператор , а не метод (см. Спецификацию языка Oak), цитата:The unary operator clone is applied to an object. (...) The clone operator is normally used inside new to clone the prototype of some class, before applying the initializers (constructors)
- три ключевых словоподобных оператора былиinstanceof new clone
(раздел 8.1, операторы). Я предполагаю, что это реальная (историческая) причинаclone
/Cloneable
mess -Cloneable
было просто более поздним изобретением, и существующийclone
код был модифицирован с ним.Реальный ответ на
цитата любезно предоставлена Джошем Блохом :
Проблема заключается исключительно в истории Java, как и в других подобных вопросах, например,
.clone()
противCloneable
.ТЛ; др
в основном по историческим причинам; текущее поведение / абстракция была введена в JDK 1.0 и не была исправлена позже, потому что это было практически невозможно сделать с поддержанием обратной совместимости кода.
Во-первых, давайте подведем итог нескольким известным фактам Java:
Hashtable
,.hashCode()
&.equals()
Были реализованы в JDK 1.0, ( Hashtable )Comparable
/Comparator
был введен в JDK 1.2 ( сопоставимый ),Теперь следует:
.hashCode()
и использовать.equals()
различные интерфейсы, сохраняя при этом обратную совместимость после того, как люди поняли, что есть абстракции лучше, чем помещать их в суперобъект, потому что, например, каждый Java-программист в 1.2 знал, что у каждогоObject
есть они, и у них оставаться там физически для обеспечения совместимости скомпилированного кода (JVM) - и добавление явного интерфейса к каждомуObject
реально реализованному им подклассу сделает этот беспорядок равным (sic!)Clonable
одному ( Блох обсуждает, почему Cloneable отстой , также обсуждался, например, в EJ 2nd и многие другие места, в том числе SO),Теперь вы можете спросить: «Что
Hashtable
со всем этим?»Ответ таков:
hashCode()
/equals()
контракт и не очень хорошие навыки языкового проектирования основных разработчиков Java в 1995/1996 гг.Цитата из Java 1.0 Language Spec от 1996 - 4.3.2 The Class
Object
, с.41:(обратите внимание , это точное утверждение было изменено в более поздних версиях, чтобы сказать, цитату:
The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
, что делает его невозможно сделать прямоеHashtable
-hashCode
-equals
соединение без чтения исторических JLS!)Команда Java решила, что им нужна хорошая коллекция в стиле словаря, и они создали
Hashtable
(пока хорошая идея), но они хотели, чтобы программист мог использовать ее с как можно меньшим количеством кода / кривой обучения (упс! Неприятности поступают!) - и, так как не было ни одного дженерики пока [это JDK 1.0 в конце концов], это будет означать, что либо каждыйObject
положить вHashtable
бы явно реализовать некоторый интерфейс (и интерфейсы были еще только в их начала тогда ... нетComparable
пока еще!) делая это сдерживающим фактором, чтобы использовать его для многих - илиObject
пришлось бы неявно реализовать какой-то метод хеширования.Очевидно, они пошли с решением 2 по причинам, изложенным выше. Да, теперь мы знаем, что они были неправы. ... в ретроспективе легко быть умным. посмеиваться
Теперь
hashCode()
требует, чтобы у каждого объекта, имеющего его, был свойequals()
метод - так что было совершенно очевидно, чтоequals()
его также нужно вставитьObject
.Поскольку по умолчанию реализаций этих методов по действительным
a
иb
Object
с, по существу бесполезна, будучи избыточными (делаетa.equals(b)
равными дляa==b
иa.hashCode() == b.hashCode()
примерно равны поa==b
также, еслиhashCode
и / илиequals
не перекрываться, или GC сотни тысячObject
с в течение всего жизненного цикла приложения 1 ) Можно с уверенностью сказать, что они были предоставлены в основном в качестве резервной меры и для удобства использования. Именно так мы добираемся до общеизвестного факта, который всегда переопределяет оба,.equals()
и.hashCode()
если вы намереваетесь фактически сравнивать объекты или хранить их в хеш-памяти, Переопределение только одного из них без другого - это хороший способ испортить ваш код (путем злых результатов сравнения или безумно высоких значений столкновений сегментов) - и обдумывание этого является источником постоянной путаницы и ошибок для начинающих (ищите ТАК, чтобы увидеть это для себя) и постоянная неприятность для более опытных.Также обратите внимание, что хотя C # работает с equals & hashcode немного лучше, сам Эрик Липперт утверждает, что они допустили почти ту же ошибку с C #, что и Sun с Java за годы до появления C # :
1, конечно,
Object#hashCode
все еще может конфликтовать, но для этого требуется немного усилий, см. Подробности в http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 и связанных отчетах об ошибках; /programming/1381060/hashcode-uniqueness/1381114#1381114 охватывает эту тему более подробно.источник
Object
дизайне России; hashability был.