HashSet‹POJO›. содержит неправильное поведение

Как часть Hadoop Mapper, у меня есть HashSet<MySimpleObject>, который содержит экземпляры очень простого класса только с двумя целочисленными атрибутами. Как и положено, я настроил hashCode() и equals():

public class MySimpleObject {

  private int i1, i2;

  public set(int i1, int i2) {
    this.i1 = i1;
    this.i2 = i2;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + i1;
    result = prime * result + i2;
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null) return false;
    if (this == obj) return true;
    if ( obj.getClass() != MySimpleObject.class ) return false;

    MySimpleObject other = (MySimpleObject)obj;
    return (this.i1 == other.i1) && (this.i2 == other.i2);
  }

Иногда каким-то образом вызовы mySet.contains(aSimpleObj) возвращают true, хотя набор на самом деле не содержит этого значения.

Я понимаю, что hashCode() сначала используется для разделения экземпляров на сегменты, а equals() вызывается только для сравнения экземпляров внутри данного сегмента.
Я пытался изменить простое значение в hasCode(), чтобы по-разному распределять экземпляры по сегментам, и увидел, что contains() все еще иногда вернул неправильный результат, но не для того же ранее ошибочного значения.
Также кажется, что это значение было затем правильно идентифицировано как выходящее за пределы набора; Поэтому я подозреваю, что что-то не так с проверкой на равенство, а не с хешированием, но я могу ошибаться...

Я в полной растерянности, и у меня нет идей. Может ли кто-нибудь пролить свет на это вообще?

----- изменить -----
некоторые пояснения:

  • i1 и i2 никогда не обновляются после построения для экземпляров, которые были добавлены в набор (хотя они иногда обновляются в другом месте кода для других экземпляров того же класса);
  • набор потенциально довольно велик (т.е. может достигать почти 15 тыс. записей), и мне интересно, может ли проблема быть связана с этим (например, переполнение ведра?).

person J. Doe    schedule 19.03.2018    source источник
comment
Возможно ли, что вы изменяете значения i1 и i2 для объектов, уже содержащихся в вашем HashSet?   -  person Eran    schedule 19.03.2018
comment
Возможно ли обновить значения i1 и i2 после добавления объектов в набор?   -  person Alexandre Dupriez    schedule 19.03.2018
comment
извините, я должен был сказать, но нет, они никогда не обновляются после построения в этой части программы   -  person J. Doe    schedule 19.03.2018
comment
Предоставьте минимальный воспроизводимый пример, который показывает, что contains() возвращает значение true, когда оно должно возвращать значение false. В нынешнем виде я не могу воспроизвести ваш результат.   -  person Daniel Pryden    schedule 19.03.2018
comment
@J.Doe: Вы говорите, что значения никогда не обновляются после построения в этой части программы. Обновляются ли они где-нибудь еще? Вы уверены, что эти изменения не произойдут после добавления элементов в набор? Например, вы используете несколько потоков? В этом случае возможно, что основной проблемой является небезопасная публикация.   -  person Daniel Pryden    schedule 19.03.2018
comment
чтобы перефразировать мой предыдущий комментарий, целочисленные атрибуты никогда не обновляются после построения для экземпляров, которые были добавлены в набор, хотя иногда они обновляются в другом месте кода для других экземпляров того же класса.   -  person J. Doe    schedule 19.03.2018
comment
Есть ли в вашем коде наследование? Мне не нравится obj.getClass() != MySimpleObject.class, он не будет управлять подклассами. Вместо этого используйте instanceof.   -  person AxelH    schedule 19.03.2018
comment
result =result+ prime * result + i1; но лучше использовать Objects.hashCode()   -  person dehasi    schedule 19.03.2018
comment
@Daniel Pryden хорошо, я приведу минимальный неудачный пример   -  person J. Doe    schedule 19.03.2018
comment
@dehasi это, конечно, не так, поскольку реализация Object hashCode() возвращает ссылку на экземпляр и, следовательно, может отличаться для двух экземпляров с одинаковыми значениями (чего не должно быть)   -  person J. Doe    schedule 19.03.2018
comment
Я имел в виду Objects.hash() извините за введение в заблуждение.   -  person dehasi    schedule 19.03.2018
comment
Mapreduce по умолчанию не использует хэш-код для вычисления сегментов. И ваш алгоритм распределен, поэтому нет ни одного экземпляра этого набора. Вам нужно использовать тип WritableComparable   -  person OneCricketeer    schedule 19.03.2018
comment
Я не согласен с тем, что hashCode() не используется Hadoop (я имею в виду javadoc для WritableComparable: Обратите внимание, что hashCode() часто используется в Hadoop для разделения ключей.); хотя на самом деле эта проблема исчезает, когда я пытаюсь воспроизвести ее в локальной подпрограмме, а не в Mapper. Однако я импортирую java.util.HashSet, поэтому я верю, что хэш-значения поступают из hashcode().   -  person J. Doe    schedule 19.03.2018


Ответы (1)


Бьюсь об заклад, у вас возникли проблемы с кратким воспроизведением этой ошибки.

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

Вы можете отладить это, временно добавив:

  1. Добавьте boolean hashCodeCalled=false в свой класс
  2. Когда вызывается hashCode(), установите hashCodeCalled=true
  3. Когда вызывается установщик, и это логическое значение равно true, затем выдается исключение или регистрируется текущая трассировка стека.

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

person WW.    schedule 19.03.2018
comment
Спасибо, WW, ваше предложение привело меня к исправлению ошибки, хотя я до сих пор не уверен, почему/где мои экземпляры мутировали. Теперь я удостоверяюсь, что создаю новую копию объекта для добавления в набор вместо того, чтобы полагаться на то, что org.hadoop.util.ReflectionUtils.newInstance(...) действительно каждый раз возвращает отдельные экземпляры, и все это работает. Хотя мне не нравится не знать, что происходит. - person J. Doe; 19.03.2018