2014-10-28 1 views
1

У меня есть следующий модульный тест: я создаю 2 разных объекта моего настраиваемого типа. Я сравниваю их хэш-коды, которые просто возвращают хэш-код их имен, то есть String.hashCode().HashMap дает тот же hashCode для различного содержимого

Затем я создаю 2 HashSets, каждый из которых содержит одну переменную и сравнивает хэш-коды наборов.

В обоих случаях хэш-коды отличаются, как и ожидалось.

Однако, если я создаю HashMap с именем в качестве индекса и значением Variable as, утверждение терпит неудачу, т. Е. Сравнивает его. Почему это?

Использование Oracle Java 1.8.

EDIT: Я могу добавить еще одну гарантию: Assert.assertNotEquals(map1, map2); также имеет место. Кроме того, я думаю, что я расцениваю это предложение правильно:

Хэш код карты определяется как сумма хэш-кодов каждого входа в entrySet карты() вид. Это гарантирует, что m1.equals (m2) подразумевает, что m1.hashCode() == m2.hashCode() для любых двух карт m1 и m2, как требуется общим контрактом Object.hashCode(). Взятые из http://docs.oracle.com/javase/7/docs/api/java/util/AbstractMap.html#hashCode%28%29

@Test 
public void test() { 
    // this assertion holds 
    Assert.assertNotEquals(new Variable("x").hashCode(), new Variable("y").hashCode()); 

    Set<Variable> set1 = new HashSet<>(); 
    set1.add(new Variable("x")); 
    Set<Variable> set2 = new HashSet<>(); 
    set2.add(new Variable("y")); 
    // this assertion also holds 
    Assert.assertNotEquals(set1.hashCode(), set2.hashCode()); 

    HashMap<String, Variable> map1 = new HashMap<>(); 
    map1.put("x", new Variable("x")); 
    HashMap<String, Variable> map2 = new HashMap<>(); 
    map2.put("y", new Variable("y")); 
    // why does this assertion fail? 
    Assert.assertNotEquals(map1.hashCode(), map2.hashCode()); 
} 

Вот определение класса переменной.

public class Variable { 
    private String name; 

    public Variable(String name) { 
     this.name = name; 
    } 

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

    @Override 
    public boolean equals(Object obj) { 
     if (obj == null || !(obj instanceof Variable)) 
      return false; 
     return name.equals(((Variable) obj).name); 
    } 
} 
+1

Почему, по вашему мнению, хэш-код должен быть другим в любом случае? –

+0

Я ожидаю, что 3-е утверждение будет выполнено, потому что первое утверждение выполнено. По той же причине, почему и второе утверждение. – hooch

+0

Использует ли конструктор 'Variable' для присваивания переданного значения' name'? –

ответ

1

реализации Oracle в обоих AbstractMap.Entry и HashMap.Entry определяют hashCode как:

public int hashCode() { 
    return (key == null ? 0 : key.hashCode())^
      (value == null ? 0 : value.hashCode()); 
} 

Уведомление оператора XOR. Если ключ и значение имеют одинаковый хэш-код, они будут отменены, когда XOR'ed, а общий хэш-код для этой записи будет 0.

Это происходит в вашем коде, потому что хеш-код для Variable является то же самое, что и строка, которую вы передаете, и эти строки такие же, как и ключи.

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

Как оказалось, это не просто теоретическая возможность. Это вполне возможно в реальных программах!

Последующий логический вопрос: как я могу избежать этого? HashMap - это своего рода таблица символов, которую мне очень нравится индексировать по имени. И atm У меня нет более полезных членов для класса Variable для включения в equals() и hashCode(). Есть идеи?

Вы можете дать Variable другой хеш-код из встроенного String. Простым способом сделать это будет использование реализации запаса, но change the multiplier from 31 для некоторого другого простого номера.

Например:

private int hash; 

@Override public int hashCode() { 
    int h = hash; 
    if (h == 0) { 
     int len = name.length(); 
     h = 1; 
     for (int i = 0; i < len; i++) { 
      h = 47*h + name.charAt(i); 
     } 
     hash = h; 
    } 
    return h; 
} 

Это модифицированная версия String.hashCode() реализации OpenJDK в. Я добавил h = 1, поэтому даже 1-символьные строки будут разными.

+0

Я согласен с этим. Но элементы отличаются попарно, а хэш-код хэш-карты определяется как сумма хэш-кодов его элементов, не так ли? Также см. Мое редактирование на исходный вопрос, где я опубликовал, что map1.equals (map2) неверен. – hooch

+0

Я вижу. Я никогда не думал, что это станет проблемой в реальном мире. Я просто изменил имя одной переменной на «asdasdfasdfasdf», и теперь утверждение выполняется так, как ожидалось. Idk, что сказать .. Я должен перепроектировать этот аспект моей программы. – hooch

+0

Итак, логический вопрос о последующем: как я могу избежать этого? HashMap - это своего рода таблица символов, которую мне очень нравится индексировать по имени. И atm У меня нет более полезных членов для класса Variable для включения в equals() и hashCode(). Есть идеи? – hooch

1

Это просто совпадение. HashMap.Node реализация (которая HashMap#hashCode() использует) из hashCode() является

public final int hashCode() { 
    return Objects.hashCode(key)^Objects.hashCode(value); 
} 

Где key и value имеют как же hashCode. Например, key - "x" и value - это объект Variable, созданный с помощью name"x" (который он использует для своего hashCode). Другими словами, "x".hashCode() и new Variable("x").hashCode() равны.

Любое value^value равно 0. Значит, hashCode вашей карты имеет вид 0 для обеих карт.

+0

Вы оба дали объяснение в то же время. Не могу принять вас обоих, извините. – hooch

+0

@hooch Не нужно извиняться. Пока вы понимаете понятия. Я просто хотел сформулировать код мудро. –

+0

Да, я отлаживал код, но слишком ленивый тоже смотрел на код, не написанный мной. Теперь, когда я это вижу, очевидно, почему это происходит. Еще раз спасибо. – hooch

Смежные вопросы