Попадание в серую область «вкл. / Выкл. По теме», но это необходимо для устранения путаницы в отношении предположения Оскара Рейеса о том, что большее количество хеш-коллизий - это хорошо, потому что это уменьшает количество элементов в HashMap. Я могу неправильно понять то, что говорит Оскар, но, похоже, я не единственный: kdgregory, delfuego, Nash0, и я, кажется, все разделяем одно (неправильное) понимание.
Если я понимаю, что Оскар говорит об одном и том же классе с тем же хэш-кодом, он предлагает, чтобы только один экземпляр класса с данным хэш-кодом был вставлен в HashMap. Например, если у меня есть экземпляр SomeClass с хэш-кодом 1 и второй экземпляр SomeClass с хэш-кодом 1, вставляется только один экземпляр SomeClass.
Пример Java pastebin на 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;
}
}
Однако хэш-код - это еще не все. Пример pastebin игнорирует тот факт, что s
иese
равны: они оба являются строкой «ese». Таким образом, вставка или получение содержимого карты с использованием s
или ese
или "ese"
в качестве ключа эквивалентны, потому чтоs.equals(ese) && s.equals("ese")
.
Второй тест демонстрирует, что ошибочный вывод о том, что идентичные хэш-коды в одном и том же классе являются причиной s -> 1
перезаписи ключа -> значение 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) имеет значение false. Но если у вас тот же класс (или, по крайней мере, .equals возвращает true), то используется та же запись. Например, new String ("one") и `new String (" one ") используются как ключи, будут использовать одну и ту же запись. На самом деле это ВСЯ точка HashMap в первую очередь! Убедитесь сами: pastebin.com/f20af40b9 - Oscar Reyes "
по сравнению с более ранними комментариями, в которых явно говорится о важности идентичного класса и одного и того же хэш-кода, без упоминания равенства:
"@delfuego: Убедитесь сами: pastebin.com/f20af40b9 Итак, в этом вопросе используется один и тот же класс (подождите, тот же класс используется правильно?) Это означает, что при использовании одного и того же хеша одна и та же запись используется, и нет «списка» записей. - Оскар Рейес »
или
"На самом деле это повысило бы производительность. Чем больше столкновений, тем меньше записей в уравнении хэш-таблицы. Меньше работы, которую нужно сделать. Это не хеш (который выглядит нормально), ни хеш-таблица (которая отлично работает), я уверен, что это на объекте создание, где производительность ухудшается. - Оскар Рейес "
или
«@kdgregory: Да, но только если столкновение происходит с разными классами, для одного и того же класса (что имеет место) используется одна и та же запись. - Оскар Рейес»
Опять же, я могу неправильно понять, что на самом деле пытался сказать Оскар. Однако его первоначальные комментарии вызвали достаточно путаницы, поэтому кажется разумным все прояснить с помощью некоторых явных тестов, чтобы не оставалось никаких сомнений.
[1] - Из « Эффективной Java», второе издание , Джошуа Блох:
Каждый раз, когда он вызывается для одного и того же объекта более одного раза во время выполнения приложения, метод hashCode должен последовательно возвращать одно и то же целое число, при условии, что информация, используемая в равных сравнениях с объектом, не изменяется. Это целое число не обязательно должно оставаться непротиворечивым от одного выполнения приложения к другому выполнению того же самого приложения.
Если два объекта равны в соответствии с методом equal s (Obj ect), то вызов метода hashCode для каждого из двух объектов должен давать одинаковый целочисленный результат.
Не требуется, чтобы, если два объекта не равны в соответствии с методом equal s (Object), тогда вызов метода hashCode для каждого из двух объектов должен давать различные целочисленные результаты. Однако программист должен знать, что получение различных целочисленных результатов для неравных объектов может улучшить производительность хеш-таблиц.