LinkedHashSet: hashCode() и equals() совпадают, но contains() нет

#java #collections #set #linkedhashset

#java #Коллекции #установить #linkedhashset

Вопрос:

Как возможно следующее:

 void contains(LinkedHashSet data, Object arg) {
    System.out.println(data.getClass()); // java.util.LinkedHashSet
    System.out.println(arg.hashCode() == data.iterator().next().hashCode()); // true
    System.out.println(arg.equals(data.iterator().next())); // true
    System.out.println(new ArrayList(data).contains(arg)); // true
    System.out.println(new HashSet(data).contains(arg)); // true
    System.out.println(new LinkedHashSet(data).contains(arg)); // true (!)
    System.out.println(data.contains(arg)); // false
}
  

Я делаю что-то не так?

Очевидно, это происходит не всегда (если вы создаете тривиальный набор объектов, вы не будете его воспроизводить). Но в моем случае это всегда происходит с более сложным классом аргументов.

РЕДАКТИРОВАТЬ: основная причина, по которой я здесь не определяю arg , заключается в том, что это довольно большой класс, сгенерированный Eclipse hashCode , который занимает 20 строк и equals в два раза длиннее. И я не думаю, что это имеет значение — до тех пор, пока они равны для двух объектов.

Комментарии:

1. Если аргументы равны set и arg , что это data ? И какие объекты вы помещаете внутрь? Если это ваши собственные объекты, вы переопределили equals() и hashCode() правильно?

2. Кроме того, почему вы проверяете contains(file) в предпоследней строке, но contains(arg) в последней? что такое file ?

3. stivlo, pushy — оба являются просто опечатками при копировании / вставке, исправлены.

Ответ №1:

Когда вы создаете свои собственные объекты и планируете использовать их в коллекции, вы всегда должны переопределять следующие методы:

 boolean equals(Object o);
int hashCode();
  

Реализация equals по умолчанию проверяет, указывают ли объекты на один и тот же объект, хотя вы, вероятно, захотите переопределить его для проверки содержимого.

Насколько это возможно, метод hashCode, определенный class Object, возвращает разные целые числа для разных объектов. Чтобы соблюдать правила, хэш-код объекта, равного другому, должен быть одинаковым, поэтому вам также необходимо переопределить хэш-код.

РЕДАКТИРОВАТЬ: я ожидал ошибочной hashCode equals реализации or, но после вашего ответа вы показали, что вы изменяете ключи после их добавления в HashSet или HashMap.

Когда вы добавляете объект в хэш-коллекцию, его хэш-код вычисляется и используется для сопоставления его с физическим местоположением в коллекции.

Если некоторые поля, используемые для вычисления хэш-кода, будут изменены, сам хэш-код изменится, поэтому реализация HashSet станет запутанной. Когда он пытается получить объект, он будет искать другое физическое местоположение и не найдет объект. Объект все равно будет присутствовать, если вы перечислите набор.

По этой причине всегда делайте ключи HashMap или HashSet неизменяемыми.

Комментарии:

1. ну, я прочитал это, на самом деле я заметил, что вы имели в виду data , set и arg , в то время как вы имели в виду только data и arg . чтобы получить дополнительную помощь, вы должны показать свой класс arg и то, как вы реализуете equals и hashCode, но, поскольку ваш средний комментарий, не ожидайте от меня помощи.

2. Это несоответствие аргументов / данных было хорошим уловом, но только ошибка копирования / вставки с моей стороны. Моя резкая реакция была вызвана тем фактом, что из описания ясно, что я знаю о hashCode и equals, и что они работают правильно в этом случае.

3. хорошо, тогда давайте успокоимся. Я думаю, что, не видя объект, невозможно ответить на этот вопрос.

4. Если класс переопределяет метод equals, а контракт с хэш-кодом требует, чтобы равные объекты имели одинаковые хэш-коды.

Ответ №2:

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

 static class MyObj {
    String s = "";

    @Override
    public int hashCode() {
        return s.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return ((MyObj) obj).s.equals(s);
    }
}

public static void main(String[] args) {
    LinkedHashSet set = new LinkedHashSet();
    MyObj obj = new MyObj();
    set.add(obj);
    obj.s = "a-ha!";
    contains(set, obj);
}
  

Этого достаточно, чтобы надежно воспроизвести его.

Объяснение: ты никогда не должен изменять поля, используемые для hashCode()!

Ответ №3:

Кажется, в вашем вопросе чего-то не хватает. Я сделал несколько предположений:

 private void testContains() {
  LinkedHashSet set = new LinkedHashSet();
  String hello = "Hello!";
  set.add(hello);
  contains(set, hello);
}

void contains(LinkedHashSet data, Object arg) {
  System.out.println(data.getClass()); // java.util.LinkedHashSet
  System.out.println(arg.hashCode() == data.iterator().next().hashCode()); // true
  System.out.println(arg.equals(data.iterator().next())); // true
  System.out.println(new ArrayList(data).contains(arg)); // true
  System.out.println(new HashSet(data).contains(arg)); // true
  System.out.println(new LinkedHashSet(data).contains(arg)); // true (!)
  System.out.println(data.contains(arg)); // true (!!)
}
  

ОТРЕДАКТИРОВАНО: чтобы отслеживать изменение вопроса!

Я по-прежнему получаю «true» для ВСЕХ, кроме первого вывода. Пожалуйста, уточните тип параметра «arg».