LinkedHashSet: hashCode () и equals () совпадают, но содержит () не - PullRequest
1 голос
/ 04 ноября 2011

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

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.

EDIT : Основная причина, по которой я не определяю arg, заключается в том, что это довольно большой класс с генерируемым Eclipse hashCode, который охватывает 20 строк и equals в два раза длиннее. И я не думаю, что это актуально - если они равны для двух объектов.

Ответы [ 3 ]

4 голосов
/ 04 ноября 2011

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

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

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

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

EDIT : я ожидал ошибочный hashCode илиequals реализации, но с момента вашего ответа вы обнаружили, что изменяете ключи после их добавления в HashSet или HashMap.

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

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

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

1 голос
/ 04 ноября 2011

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

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 ()!

0 голосов
/ 04 ноября 2011

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

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 (!!)
}

ИЗД.Пожалуйста, уточните тип параметра arg.

...