Почему моя хэш-карта не работает? Мой объект имеет свойство, согласно которому равенство hashCode() подразумевает равенство equals()

Почему моя Java HashMap не работает? Мой объект обладает тем свойством, что равенство .equals подразумевает равенство hashCode.

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


person interestedparty333    schedule 15.06.2017    source источник
comment
как указано в ответе на этот вопрос внизу: вам нужен окончательный хэш-код, если вы используете коллекции на основе хэшей   -  person Stefan Warminski    schedule 15.06.2017
comment
Используете ли вы один и тот же объект в качестве ключа и значения вашей хэш-карты? Если да, то что вы хотите заархивировать? Обычно ключ вообще не следует менять после добавления записи в хэш-карту.   -  person Erich Kitzmueller    schedule 15.06.2017
comment
Необходимое свойство заключается в том, что равенство объектов подразумевает равенство хэш-кода, а не наоборот. Вы не можете изменить поле объекта, если объект используется в качестве ключа, а поле является частью вычислений хэш-кода или равенства.   -  person user207421    schedule 15.06.2017
comment
Где твой код? Как мы могли узнать, что вы делаете неправильно, не имея возможности увидеть, что вы пробовали до сих пор?   -  person fps    schedule 15.06.2017
comment
Эх, код не нужен. Просто пытаюсь улучшить ситуацию с результатами поиска для других. Я отмечу ответ как принятый, чтобы он больше не отображался в ваших лентах открытых вопросов.   -  person interestedparty333    schedule 16.06.2017
comment
Нет, очень нужен код. Это очень плохой вопрос на данный момент. Пожалуйста, прочитайте codeblog.jonskeet.uk/2010/08/29 /writing-the-perfect-question (В вашем текущем вопросе даже не указано, что одно из изменяемых полей является частью проверки на равенство и хеш-кода.) Должно быть очень просто предоставить < href="https://stackoverflow.com/help/minimal-reproducible-example">минимальный воспроизводимый пример, который значительно улучшит вопрос.   -  person Jon Skeet    schedule 16.06.2017


Ответы (2)


Можно предположить, что я изменяю поле объекта после добавления объекта в HashMap.

Именно поэтому.

Javadoc говорит:

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

«Не указано» означает «может не работать», так что не удивляйтесь, если это не сработает.

person Andreas    schedule 15.06.2017

Если вы мутируете (изменяете) объекты после добавления их в HashMap, ваши объекты окажутся не в том сегменте. Это связано с тем, что даже если объект изменился, он не "повторно заполняется" в (новую) правильную корзину после внесения изменений.

Таким образом, такие методы, как remove, не смогут найти ваш объект, потому что объект находится в устаревшем ведре.

Что действительно приводит меня в чувство (для меня), так это то, что когда объект изначально добавляется в HashMap, хэш-значение (int) сохраняется вместе с Entry. Это означает, что хэш является относительно статичным свойством, которое редко (никогда) не обновляется.

Возможные обходные пути включают в себя:

  1. Вероятно, это сработает, если вы переберете каждую запись в hashmap и создадите новую HashMap из этих записей. Это, вероятно, плохо с точки зрения производительности, но, вероятно, самое правильное, что можно сделать.

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

  3. Алгоритмическое решение: Найдите способ сохранить другую часть данных в объекте, который действует как постоянный ключ. Например, в моем приложении я отслеживал «возраст» объекта с помощью целочисленного поля. Каждый раз, когда я достигал определенного количества времени в своем приложении, я увеличивал возраст всех ключей. Если что-то было старше 50 единиц времени, то я мог разумно выбросить пару ключ-значение. В качестве альтернативы я мог бы изменить объект, чтобы сохранить «время рождения» объекта. То есть, если приложению было 1000 единиц времени, когда объект был создан, я бы сохранил число 1000. Затем, чтобы определить возраст объекта, я мог бы сравнить текущее «время» со временем рождения и выбросить объект, если разница была> 50.

person interestedparty333    schedule 15.06.2017
comment
Исключение изменяемого поля только из hashCode не решит проблему. Да, hashCode найдет правильное ведро, но логика поиска должна подтвердить, что это правильный объект (поскольку несколько ключей могут отображаться в этом ведре), и для проверки этого будет использоваться равенство. Если equals по-прежнему использует изменяемое поле, он не сможет распознать предполагаемый ключ. - person bowmore; 15.06.2017
comment
Поскольку сравнение включает в себя ключ, оно все равно будет работать. Я обновил свой ответ, чтобы более серьезно отразить проблемы, с которыми может столкнуться использование неопределенного поведения компилятора. - person interestedparty333; 16.06.2017
comment
Вы правы, это сработает, однако теперь я не уверен, какое неопределенное поведение компилятора, по вашему мнению, имеет место. - person bowmore; 16.06.2017
comment
Ах, я просто ссылаюсь на цитату Андреаса из Map javadoc: поведение карты не указано, если значение объекта изменяется таким образом, что это влияет на сравнение с равным, в то время как объект является ключом на карте. - person interestedparty333; 16.06.2017