Подтвердить что ты не робот

Если изменчивые коллекции переопределяют равные и hashCode?

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

4b9b3361

Ответ 1

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

(На самом деле у меня мало опыта Java. Этот ответ основан на С#.)

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

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

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

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

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

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

(java doc of Map) ).

Ответ 7

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

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

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

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

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

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

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

Ответ 8

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