Реальный ответ на
почему есть Comparator
интерфейс, а нет Hasher
и Equator
?
цитата любезно предоставлена Джошем Блохом :
Оригинальные API Java были сделаны очень быстро в сжатые сроки, чтобы соответствовать закрывающемуся окну рынка. Оригинальная команда Java проделала невероятную работу, но не все API идеальны.
Проблема заключается исключительно в истории Java, как и в других подобных вопросах, например, .clone()
против Cloneable
.
ТЛ; др
в основном по историческим причинам; текущее поведение / абстракция была введена в JDK 1.0 и не была исправлена позже, потому что это было практически невозможно сделать с поддержанием обратной совместимости кода.
Во-первых, давайте подведем итог нескольким известным фактам Java:
- Java с самого начала и до наших дней гордо поддерживала обратную совместимость, что требовало поддержки устаревших API в новых версиях,
- Таким образом, почти каждая языковая конструкция, представленная в JDK 1.0, дожила до наших дней,
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),
- они просто оставили их там, чтобы у будущего поколения был постоянный источник WTF.
Теперь вы можете спросить: «Что Hashtable
со всем этим?»
Ответ таков: hashCode()
/ equals()
контракт и не очень хорошие навыки языкового проектирования основных разработчиков Java в 1995/1996 гг.
Цитата из Java 1.0 Language Spec от 1996 - 4.3.2 The Class Object
, с.41:
Методы equals
и hashCode
объявлены в пользу хеш- java.util.Hashtable
таблиц, таких как (§21.7). Метод equals определяет понятие равенства объектов, основанное на сравнении, а не на ссылке.
(обратите внимание , это точное утверждение было изменено в более поздних версиях, чтобы сказать, цитату: 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 # :
Но почему так должно быть, что каждый объект должен иметь возможность хэшировать себя для вставки в хеш-таблицу? Кажется странной вещью требовать, чтобы каждый объект был в состоянии сделать. Я думаю, что если бы мы сегодня проектировали систему типов с нуля, хеширование могло бы быть сделано иначе, возможно, с помощью IHashable
интерфейса. Но когда была разработана система типов CLR, не было общих типов, и поэтому хэш-таблица общего назначения должна была хранить любой объект.
1, конечно, Object#hashCode
все еще может конфликтовать, но для этого требуется немного усилий, см. Подробности в http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 и связанных отчетах об ошибках; /programming/1381060/hashcode-uniqueness/1381114#1381114 охватывает эту тему более подробно.
Person
реализации ожидаемогоequals
иhashCode
поведения. Вы бы тогда имелиHashMap<PersonWrapper, V>
. Это один пример, когда подход чистого ООП не элегантен: не каждая операция над объектом имеет смысл как метод этого объекта. Вся ЯвыObject
типа представляет собой совокупность различных обязанностей - толькоgetClass
,finalize
иtoString
методы кажутся оправданными удаленно с помощью современных передовых методов.