Потрапляючи в сіру область "теми / вимкнення", але необхідно усунути плутанину щодо припущення Оскара Рейєса про те, що більше хеш-зіткнень - це хороша річ, оскільки це зменшує кількість елементів у HashMap. Я можу неправильно зрозуміти, що говорить Оскар, але я, здається, не єдиний: kdgregory, delfuego, Nash0, і я, схоже, поділяю однакове (неправильне) розуміння.
Якщо я розумію, що говорить Оскар про той самий клас із тим самим хеш-кодом, він пропонує лише один екземпляр класу із заданим хеш-кодом вставити в HashMap. Наприклад, якщо у мене є екземпляр SomeClass з хеш-кодом 1 і другий екземпляр SomeClass з хеш-кодом 1, вставляється лише один екземпляр SomeClass.
Приклад пастбіну Java на веб-сайті http://pastebin.com/f20af40b9, схоже, вказує на сказане вище, резюмує те, що пропонує Оскар.
Незалежно від розуміння чи непорозуміння, що трапляється в різних примірниках одного класу, не вставляйте лише один раз у HashMap, якщо вони мають один і той же хеш-код - не поки не буде визначено, чи рівні ключі чи ні. Контракт хеш-коду вимагає, щоб рівні об'єкти мали однаковий хеш-код; однак, не потрібно, щоб у неоднакових об’єктів були різні хеш-коди (хоча це може бути бажано з інших причин) [1].
Приклад pastebin.com/f20af40b9 (на який Оскар посилається щонайменше двічі) слідує, але трохи змінений, щоб використовувати твердження JUnit, а не лінії друку. Цей приклад використовується для підтримки пропозиції про те, що одні й ті ж хеш-коди викликають зіткнення, а коли класи однакові, створюється лише один запис (наприклад, лише один рядок у цьому конкретному випадку):
@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
String s = new String("ese");
String ese = new String("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// AND equal
assertTrue(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(2, map.size());
assertEquals(2, map.get("ese"));
assertEquals(3, map.get(some));
assertTrue(s.equals(ese) && s.equals("ese"));
}
class SomeClass {
public int hashCode() {
return 100727;
}
}
Однак хеш-код не є повною історією. Що зневажає приклад пастбіну, це той факт, що обидва s
і ese
рівні: вони обидва є рядком "ese". Таким чином, вставка або отримання вмісту карти з допомогою s
або ese
чи в "ese"
якості ключа все еквівалентні , так як s.equals(ese) && s.equals("ese")
.
Другий тест демонструє помилковість висновку, що однакові хеш-коди одного класу є причиною s -> 1
перезапису значень key -> , ese -> 2
коли map.put(ese, 2)
викликається в тестовому. У тесті два, s
і ese
досі є однаковий хеш-код (як це перевірено assertEquals(s.hashCode(), ese.hashCode());
) І вони одного класу. Однак, s
і ese
це MyString
випадки цього тесту, а не String
екземпляри Java - єдиною різницею, що стосується цього тесту, є рівно: String s equals String ese
у тесті один вище, тоді як MyStrings s does not equal MyString ese
у тесті два:
@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
MyString s = new MyString("ese");
MyString ese = new MyString("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// BUT not equal
assertFalse(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(3, map.size());
assertEquals(1, map.get(s));
assertEquals(2, map.get(ese));
assertEquals(3, map.get(some));
}
/**
* NOTE: equals is not overridden so the default implementation is used
* which means objects are only equal if they're the same instance, whereas
* the actual Java String class compares the value of its contents.
*/
class MyString {
String i;
MyString(String i) {
this.i = i;
}
@Override
public int hashCode() {
return 100727;
}
}
Виходячи з більш пізнього коментаря, Оскар, здається, перевернув сказане раніше і визнає важливість рівних. Однак все ще здається, що поняття, що дорівнює, - це те, що має значення, а не "той самий клас", є незрозумілим (акцент мій):
"Не насправді. Список створюється лише в тому випадку, якщо хеш однаковий, але ключ інший. Наприклад, якщо String дає хеш-код 2345, а Integer дає той самий хеш-код 2345, тоді ціле число вставляється в список через String. equals (Integer) - помилково. Але якщо у вас той самий клас (або принаймні .equals повертається true), використовується той самий запис. Наприклад, новий String ("one") і `new String (" one "), який використовується як ключі, буде використовувати той самий запис. Насправді це в цілому точка HashMap! Перш за все, переконайтесь у цьому: pastebin.com/f20af40b9 - Оскар Рейєс "
порівняно з попередніми коментарями, які явно стосуються важливості однакового класу та одного і того ж хеш-коду, без згадки про рівність:
"@delfuego: Побачте самі: pastebin.com/f20af40b9 Отже, у цьому питанні використовується той самий клас (почекайте хвилину, той самий клас використовується правильно?), що означає, що коли для того ж хеша використовується той самий запис використовується і немає "списку" записів. - Оскар Рейєс "
або
"Насправді це призведе до підвищення продуктивності. Чим більше зіткнень еквівалентно менше записів у хешбелевому еквіваленті. Менше роботи. Не хеш (який виглядає чудово), ні хештинг (який чудово працює) творіння там, де виступ принижує гідність - Оскар Рейєс "
або
"@kdgregory: Так, але тільки якщо зіткнення трапляється з різними класами, для одного і того ж класу (у тому випадку) використовується один і той же запис. - Оскар Рейєс"
Знову ж таки, я можу неправильно зрозуміти, що насправді намагався сказати Оскар. Однак його оригінальні коментарі викликали достатню плутанину, що здається доцільним все прояснити явними тестами, щоб не було затяжних сумнівів.
[1] - З ефективної Java, друге видання Джошуа Блоха:
Кожного разу, коли він під час виконання програми викликає один і той самий об'єкт не один раз, метод hashCode повинен послідовно повертати одне ціле ціле число, за умови, що жодна інформація, що використовується в однакових порівняннях об'єкта, не змінюється. Це ціле число не повинно залишатися послідовним від одного виконання програми до іншого виконання тієї самої програми.
Якщо два об'єкти рівні за методом рівних s (Obj ect), то виклик методу hashCode на кожному з двох об'єктів повинен давати однаковий цілий результат.
Не потрібно, якщо два об'єкти неоднакові за рівним методом s (Object), то виклик методу hashCode на кожному з двох об'єктів повинен отримати чіткі цілі результати. Однак програмісту слід пам’ятати, що створення чітких цілих результатів для неоднакових об’єктів може покращити продуктивність хеш-таблиць.