Хеш-код разных объектов одинаковый

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

CustomObject oj1 = new CustomObject();
oj1.setId(1234L);

CustomObject oj2 = new CustomObject();
oj2.setId(9999L);

System.out.println(oj1); //Prints CustomObject@1
System.out.println(oj2); //Prints CustomObject@1

System.out.println(oj1.hashCode()); //Prints 1
System.out.println(oj2.hashCode()); //Prints 1

Я заметил эту проблему после того, как понял, что один из модульных тестов с переменной HashSet добавлял только самый первый объект и игнорировал остальные. Очевидно, что hashSet делает то, что должен делать, но объекты не должны быть одинаковыми и являются новыми экземплярами с разными идентификаторами. Я тестировал то же самое за пределами модульного теста в приложении, и все та же проблема. Как только я вернусь к старому коду зависимости, он будет вести себя нормально, и приведенные выше операторы печати показывают разные числа! Я уверен, что одна из зависимостей вызывает эту проблему, но я не могу определить основную причину. CustomObject извлекается косвенно через ту же зависимость и не имеет реализованных equals () и hashcode (), он имеет только

private static final long serialVersionUID = 1L;

Глядя на источник CustomObject, можно увидеть эту реализацию

public class CustomObject extends BaseModel implements Serializable

и в BaseModel определены методы equals и hashCode.

import org.jvnet.jaxb2_commons.lang.*;
import org.jvnet.jaxb2_commons.locator.ObjectLocator;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import java.io.Serializable;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BaseModel")
@XmlSeeAlso({
CustomObject.class
})
public abstract class BaseModel implements Serializable, Equals2, HashCode2
{

    private final static long serialVersionUID = 1L;

    public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy2 strategy) {
        if ((object == null)||(this.getClass()!= object.getClass())) {
            return false;
        }
        if (this == object) {
            return true;
        }
        return true;
    }

    public boolean equals(Object object) {
        final EqualsStrategy2 strategy = JAXBEqualsStrategy.INSTANCE;
        return equals(null, null, object, strategy);
    }

    public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
        int currentHashCode = 1;
        return currentHashCode;
    }

    public int hashCode() {
        final HashCodeStrategy2 strategy = JAXBHashCodeStrategy.INSTANCE;
        return this.hashCode(null, strategy);
    }

}

Заранее спасибо.

HashSet в библиотеке коллекций Java поддерживает несколько объектов с одним и тем же хеш-кодом, но не так эффективно, как если бы они были хорошо распределены по хэш-пространству. Похоже, что объекты предназначены для использования нет в качестве ключей в коллекциях на основе хэшей (иначе это ошибка). Пожалуйста, укажите также конкретную реализацию Set, которую вы используете. Возможно ли, что реализация equals(Object) тоже «плохо себя ведет»?

William Price 19.05.2018 04:42

Вы хотите сказать, что если вы запустите приведенный выше фрагмент кода точно так же, как он был опубликован (кроме New, который должен быть new), вы получите результат, о котором говорите? А у CustomObject нет подмененного метода hashCode или toString?

Erwin Bolwidt 19.05.2018 04:44

Установите <CustomObject> myHashSet = new HashSet <> ();

al gh 19.05.2018 04:45

Я попробовал этот фрагмент кода внутри модульного теста и внутри приложения. Как я уже упоминал, это пакетное задание, поэтому для целей тестирования я помещаю его в класс задания, который реализует BatchJob <JobExecution>.

al gh 19.05.2018 04:48

Еще раз спасибо всем. Как вы все упомянули, настоящая проблема была в родительском классе, которого я сначала не заметил.

al gh 19.05.2018 06:52
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
5
179
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Два неравных объекта могут иметь один и тот же хэш-код. Фактически, это было разрешено математическим требованием. Подумайте, например, о строках: существует бесконечно много неравных строк («a», «aa», «aaa» ...), но только 2 ^ 32 возможных значения int. Очевидно, что должны быть разные строки, которые имеют общий хэш-код.

Но HashSet знает об этом, поэтому он использует результат equals, а также хэш-код. Если добавляется только один из объектов, то у них не просто один и тот же хэш-код - они равны, как это возвращает метод equals. Мы не можем определить, почему это так, не говоря уже о том, преднамеренно ли это, без кода настраиваемого класса.

договор на объект говорит, что одинаковые объекты должны иметь одинаковый хэш-код. Но обратное неверно: объекты с одинаковым хэш-кодом не обязательно должны быть равны. В Javadocs прямо говорится об этом:

  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

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

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

William Price 19.05.2018 04:39

Очевидно, что что-то изменилось в базовом классе, и вам просто нужно будет найти это и исправить, иначе реализовать hashCode() и equals() в этом классе приемлемо.

Кто-то где-то реализовал hashCode() для возврата 1, что является идиотизмом. Лучше было бы вообще этого не реализовывать. И найти это несложно. Просто посмотрите Javadoc для CustomObject и посмотрите, откуда он унаследован от hashCode().

На ваш взгляд, да, его родительский класс имеет equals и определен hashCode

al gh 19.05.2018 05:26

Проблема здесь в реализации класса CustomObject (или одного из его предков). Автор CustomObject (или один из его предков) неправильно переопределил методы toString, hashCode и equals, не понимая их семантики и последствий. Вот ваши варианты (не обязательно в указанном порядке) для решения проблемы:

  1. Вы должны уведомить автора вашей библиотеки зависимостей о проблеме в классе CustomObject и правильно реализовать или переопределить методы toString, hashCode и equals. Но помните - автор кода зависимости может снова вернуть вас в это место в будущем.
  2. Предполагая, что класс CustomObject не является final - расширьте CustomObject (обратите внимание - его лучше называть как CustomClass, а не CustomObject), чтобы иметь правильную реализацию методов toString, hashCode и equals. Используйте этот расширенный класс в своем коде вместо класса CustomObject. Это даст вам лучший контроль, потому что ваш код зависимости не может снова вызвать эту проблему.
  3. Используйте AOP, чтобы ввести переопределенную и правильную реализацию методов toString, hashCode и equals в классе CustomObject. Этот подход также является перспективным, как и вариант 2 выше.

Спасибо, фактическое имя другое и не CustomObject

al gh 19.05.2018 05:32

«CustomObject ... не поддерживает equals() и hashCode()». Прочтите вопрос.

user207421 19.05.2018 05:34

EJP - Спасибо за комментарий. Ваш комментарий помог мне улучшить свой ответ. Вопрос был отредактирован после того, как я опубликовал ответ. Я отредактировал ответ, чтобы он соответствовал отредактированному вопросу.

RaviH 19.05.2018 05:47

Нет. Заявление, которое я процитировал, было в исходном вопросе до любого редактирования.

user207421 19.05.2018 05:49

EJP - Я этого не заметил. Вы выигрываете, я проигрываю :-) Пожалуйста, смотрите на суть заявлений, а не на дословный текст.

RaviH 19.05.2018 06:01

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

user207421 19.05.2018 06:06
Ответ принят как подходящий

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

  1. Ваш CustomClass расширяет BaseClass.

  2. BaseClass заменяет Object::hashCode()

  3. Переопределение в версии BaseClass, которую вы нам показали всегда будет возвращать 1. Он вызывает метод hashCode(ObjectLocator, HashCodeStrategy2) с определенной стратегией, но реализация этого метода просто игнорирует аргумент стратегии.

Теперь довольно ясно, что эта версия кода BaseClass может возвращать только 1 в качестве хэш-кода. Но вы говорите, что ваш код работал, а вы только изменили зависимость. Из этого мы должны сделать вывод, что зависимость изменилась, и что новая версия зависимости сломана.


Если в этом есть что-то «странное», так это то, что кто-то решил реализовать (новый) BaseClass таким образом и выпустить его, не тестируя должным образом.


На самом деле, есть возможный способ заставить ваш CustomClass работать. Метод BaseClass::hashCode(ObjectLocator, HashCodeStrategy2) является общедоступным, а не окончательным, поэтому вы можете переопределить его на своем CustomClass следующим образом :.

@Override
public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
    return System.identityHashCode(this);
}

И действительно, возможно, разработчики BaseClass намереваются сделать это. Но все же я бы сказал, что BaseClass сломан:

  • Кодирование класса так, чтобы hashCode возвращал 1, поскольку поведение по умолчанию является неприятным.
  • В BaseClass нет документации javadoc, объясняющей необходимость переопределения метода.
  • Их (необъявленное?) Изменение поведения BaseClass - это изменение Нарушение API. Вы не должны делать такие вещи без уважительной причины и без предупреждения.
  • Поведение по умолчанию соответствующего метода equals объективно неверно.

Действительно. Стратегия в целом странна. Он предоставляется как в hashCode(), так и в equals(), а затем полностью игнорируется. Как вы думаете, это сгенерированный код? Я не знаю, что делает JAXB.

user207421 19.05.2018 05:45

Другие вопросы по теме