Должны ли изменяемые коллекции переопределять equals и hashCode? - PullRequest
24 голосов
/ 09 марта 2011

Мне просто интересно, было ли хорошей идеей переопределить equals и hashCode для изменяемых коллекций. Это будет означать, что если я вставлю такую ​​коллекцию в HashSet, а затем изменю коллекцию, HashSet больше не сможет найти коллекцию. Означает ли это, что только неизменяемые коллекции должны переопределять equals и hashCode, или это просто неприятные Java-программисты?

Ответы [ 8 ]

5 голосов
/ 09 марта 2011

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

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

5 голосов
/ 09 марта 2011

Вы должны переопределить equals и hashCode, если ваш класс должен вести себя как тип значения.Обычно это не относится к коллекциям.

(Я не очень разбираюсь в Java. Этот ответ основан на C #.)

2 голосов
/ 09 марта 2011

Я думаю, что больший вопрос в том, что должно произойти, если кто-то попытается добавить экземпляр вашей FredCollection в набор дважды.

FredCollection c = ...
set.add(c);
set.add(c);

Должно ли size() из set быть 2 или 1 после этого?

Вам когда-нибудь понадобится проверить "равенство" двух разных экземпляров FredCollection? Я думаю, что ответ на этот вопрос важнее при определении вашего поведения equals() / hashcode(), чем что-либо еще.

2 голосов
/ 09 марта 2011

Это так же, как с любым изменяемым классом. Когда вы вставляете экземпляр в HashSet, а затем вызываете мутирующий метод, у вас возникают проблемы. Итак, мой ответ: да, если есть смысл.

Конечно, вы можете использовать неизменяемый Wrapper для своей Коллекции перед добавлением его в HashSet.

1 голос
/ 09 марта 2011

Не следует переопределять equals и hashCode, чтобы они отражали изменяемый член.

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

  • технический: равны, если они представляют один и тот же объект, который отличается от тем, что один и тот же объект (если вы думаете о прокси, сериализации, удаленных вещах ...)
  • логика бизнеса: они равны, если выглядят одинаково (один и тот же атрибут) - здесь важно то, что даже в одном приложении нет единственного определения равенства даже для одного и того же класса. (Пример вопроса: когда два камня равны?))

Но так как equals используется техническими средствами (HashMap), вы должны реализовать его техническим способом и построить бизнес-логику, связанную с равными, чем-то другим (что-то вроде интерфейса компаратора). А для вашей коллекции это означает: не переопределяйте equals и hashCode (таким образом, что нарушает технический контракт:

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

(Java-карта) ).

1 голос
/ 09 марта 2011

Это проблема не только для коллекций, но и для изменяемых объектов в целом (другой пример: Point2D). И да, это потенциальная проблема, которую в конечном итоге учат программисты на Java.

0 голосов
/ 29 июля 2014

equals используется для добавления / удаления элементов из коллекций, таких как CopyOnWriteArraySet, HashSet, если hashCode равен для двух разных объектов и т. Д. Equals должен быть симметричным, т.е. если B.equals (C) возвращает true, тогда C.equals (B)должен вернуть тот же результат.В противном случае ваши добавления / удаления на этих наборах XXX ведут себя непонятно.Проверьте Переопределение равно для CopyOnWriteArraySet.add и удалите , чтобы выяснить, как неправильное переопределение равно влияет на операции добавления / удаления в коллекциях

0 голосов
/ 04 августа 2013

Основная проблема с equals и hashCode заключается в том, что есть два логических способа определения отношения эквивалентности; некоторые потребители этого класса захотят одно определение, в то время как другие потребители того же класса захотят другое.

Я бы определил два отношения эквивалентности следующим образом:

  • Две ссылки на объекты X и Y полностью эквивалентны, если перезапись X со ссылкой на Y не изменит текущего или будущего поведения каких-либо членов X или Y.

  • Две ссылки на объекты X и Y имеют эквивалентное состояние, если в программе, которая не сохранила значения, возвращенные из хеш-функции, связанной с идентификацией, замена всех ссылок на X со всеми ссылками на Y оставит состояние программы без изменений.

Обратите внимание, что второе определение в основном относится к общему сценарию, где две вещи содержат ссылки на объекты некоторого изменяемого типа (например, массивы), но могут быть уверены, что, по крайней мере, в течение определенного периода времени, представляющего интерес, эти объекты не будут подвергаться воздействию чего-либо, что может их изменить. В таком сценарии, если объекты «держателя» эквивалентны во всех других отношениях, их эквивалентность должна зависеть от того, соответствуют ли объекты, которые они содержат, определению эквивалентности second выше.

Обратите внимание, что второе определение не касается каких-либо подробностей того, как может измениться состояние объекта. Кроме того, обратите внимание, что неизменяемые объекты могут либо для определения эквивалентности сообщать о различных объектах с равным содержанием как о равных, так и неравных (если различие между X и Y и only состоит в том, что X.Equals (X) сообщает об истинном) в то время как X.Equals (Y) сообщает false, это было бы разницей, но, вероятно, было бы наиболее полезно, чтобы такие объекты использовали ссылочный идентификатор для первого отношения эквивалентности и эквивалентность других аспектов для второго.

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

...