Я столкнулся с странным результатом в приложении 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);
}
}
Заранее спасибо.
Вы хотите сказать, что если вы запустите приведенный выше фрагмент кода точно так же, как он был опубликован (кроме New, который должен быть new), вы получите результат, о котором говорите? А у CustomObject нет подмененного метода hashCode или toString?
Установите <CustomObject> myHashSet = new HashSet <> ();
Я попробовал этот фрагмент кода внутри модульного теста и внутри приложения. Как я уже упоминал, это пакетное задание, поэтому для целей тестирования я помещаю его в класс задания, который реализует BatchJob <JobExecution>.
Еще раз спасибо всем. Как вы все упомянули, настоящая проблема была в родительском классе, которого я сначала не заметил.




Два неравных объекта могут иметь один и тот же хэш-код. Фактически, это было разрешено математическим требованием. Подумайте, например, о строках: существует бесконечно много неравных строк («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 указывает, что они помещаются в набор на основе хешей.
Очевидно, что что-то изменилось в базовом классе, и вам просто нужно будет найти это и исправить, иначе реализовать hashCode() и equals() в этом классе приемлемо.
Кто-то где-то реализовал hashCode() для возврата 1, что является идиотизмом. Лучше было бы вообще этого не реализовывать. И найти это несложно. Просто посмотрите Javadoc для CustomObject и посмотрите, откуда он унаследован от hashCode().
На ваш взгляд, да, его родительский класс имеет equals и определен hashCode
Проблема здесь в реализации класса CustomObject (или одного из его предков). Автор CustomObject (или один из его предков) неправильно переопределил методы toString, hashCode и equals, не понимая их семантики и последствий. Вот ваши варианты (не обязательно в указанном порядке) для решения проблемы:
CustomObject и правильно реализовать или переопределить методы toString, hashCode и equals. Но помните - автор кода зависимости может снова вернуть вас в это место в будущем.CustomObject не является final - расширьте CustomObject (обратите внимание - его лучше называть как CustomClass, а не CustomObject), чтобы иметь правильную реализацию методов toString, hashCode и equals. Используйте этот расширенный класс в своем коде вместо класса CustomObject. Это даст вам лучший контроль, потому что ваш код зависимости не может снова вызвать эту проблему.AOP, чтобы ввести переопределенную и правильную реализацию методов toString, hashCode и equals в классе CustomObject. Этот подход также является перспективным, как и вариант 2 выше.Спасибо, фактическое имя другое и не CustomObject
«CustomObject ... не поддерживает equals() и hashCode()». Прочтите вопрос.
EJP - Спасибо за комментарий. Ваш комментарий помог мне улучшить свой ответ. Вопрос был отредактирован после того, как я опубликовал ответ. Я отредактировал ответ, чтобы он соответствовал отредактированному вопросу.
Нет. Заявление, которое я процитировал, было в исходном вопросе до любого редактирования.
EJP - Я этого не заметил. Вы выигрываете, я проигрываю :-) Пожалуйста, смотрите на суть заявлений, а не на дословный текст.
Я не знаю, что это должно значить. И текст дословно, и суть вашего ответа были неправильными, когда вы его разместили.
Я думаю, вы уже поняли, каков ответ на ваш вопрос, но код, который вы добавили в своем обновлении, дает понять:
Ваш CustomClass расширяет BaseClass.
BaseClass заменяет Object::hashCode()
Переопределение в версии 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.
HashSet в библиотеке коллекций Java поддерживает несколько объектов с одним и тем же хеш-кодом, но не так эффективно, как если бы они были хорошо распределены по хэш-пространству. Похоже, что объекты предназначены для использования нет в качестве ключей в коллекциях на основе хэшей (иначе это ошибка). Пожалуйста, укажите также конкретную реализацию Set, которую вы используете. Возможно ли, что реализация
equals(Object)тоже «плохо себя ведет»?