хеш-код разных объектов одинаков - PullRequest
0 голосов
/ 19 мая 2018

Я столкнулся со странным результатом в 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);
    }

}

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

Ответы [ 4 ]

0 голосов
/ 19 мая 2018

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

  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, поскольку поведение по умолчанию является неприятным.
  • В * нет javadoc1047 * для объяснения необходимости переопределить метод.
  • Их (необъявленное?) Изменение поведения BaseClass является API, нарушающим изменение.Вы не должны делать такие вещи без веской причины и без предупреждения.
  • Поведение по умолчанию соответствующего метода equals объективно неверно.
0 голосов
/ 19 мая 2018

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

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

0 голосов
/ 19 мая 2018

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

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

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

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

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

  • Требуется , а не , если два объекта являются неравными в соответствии с методом equals (java.lang.Object), а затем вызыватьМетод hashCode для каждого из двух объектов должен давать разные целочисленные результаты.Однако программист должен знать, что выдача различных целочисленных результатов для неравных объектов может повысить производительность хеш-таблиц.

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

...