Как мы решаем наилучшую реализацию метода hashCode()
для коллекции (при условии, что метод equals правильно переопределен)?
Лучшая реализация метода hashCode для коллекции
Ответ 1
Лучшая реализация? Это сложный вопрос, потому что это зависит от модели использования.
Практически во всех случаях разумная хорошая реализация была предложена в Josh Bloch Effective Java в пункте 8 (второе издание). Лучше всего искать это там, потому что автор объясняет, почему подход хорош.
Короткая версия
-
Создайте результат типа
int result
и присвойте ненулевое значение. -
Для каждого поля
f
проверенного в методеequals()
, вычислите хеш-кодc
:- Если поле f является
boolean
: вычислить(f? 0: 1)
; - Если поле f является
byte
,char
,short
илиint
: вычисления(int)f
; - Если поле f
long
: вычислить(int)(f ^ (f >>> 32))
; - Если поле f является
float
: вычислитьFloat.floatToIntBits(f)
; - Если поле f
double
: вычислитеDouble.doubleToLongBits(f)
и обработайте возвращаемое значение, как любое длинное значение; - Если поле f является объектом: используйте результат метода
hashCode()
или 0, еслиf == null
; - Если поле f является массивом: рассмотрите каждое поле как отдельный элемент и вычислите значение хеш-функции рекурсивным способом и объедините значения, как описано далее.
- Если поле f является
-
Объедините хеш-значение
c
сresult
:result = 37 * result + c
-
Вернуть
result
Это должно привести к правильному распределению значений хеш-функции для большинства ситуаций использования.
Ответ 2
Если вас устраивает реализация Effective Java, рекомендованная dmeister, вы можете использовать библиотечный вызов вместо собственного:
@Override
public int hashCode() {
return Objects.hashCode(this.firstName, this.lastName);
}
Для этого требуется либо Guava (com.google.common.base.Objects.hashCode
), либо стандартная библиотека в Java 7 (java.util.Objects.hash
), но она работает аналогичным образом.
Ответ 3
Лучше использовать функциональность, предоставляемую Eclipse, которая выполняет довольно хорошую работу, и вы можете приложить свои усилия и энергию для развития бизнес-логики.
Ответ 4
Хотя это связано с документацией по Android
(Wayback Machine) и моим собственным кодом на Github, в целом это будет работать для Java. Мой ответ - это расширение dmeister Answer, содержащее только код, который намного легче читать и понимать.
@Override
public int hashCode() {
// Start with a non-zero constant. Prime is preferred
int result = 17;
// Include a hash for each field.
// Primatives
result = 31 * result + (booleanField ? 1 : 0); // 1 bit » 32-bit
result = 31 * result + byteField; // 8 bits » 32-bit
result = 31 * result + charField; // 16 bits » 32-bit
result = 31 * result + shortField; // 16 bits » 32-bit
result = 31 * result + intField; // 32 bits » 32-bit
result = 31 * result + (int)(longField ^ (longField >>> 32)); // 64 bits » 32-bit
result = 31 * result + Float.floatToIntBits(floatField); // 32 bits » 32-bit
long doubleFieldBits = Double.doubleToLongBits(doubleField); // 64 bits (double) » 64-bit (long) » 32-bit (int)
result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));
// Objects
result = 31 * result + Arrays.hashCode(arrayField); // var bits » 32-bit
result = 31 * result + referenceField.hashCode(); // var bits » 32-bit (non-nullable)
result = 31 * result + // var bits » 32-bit (nullable)
(nullableReferenceField == null
? 0
: nullableReferenceField.hashCode());
return result;
}
РЕДАКТИРОВАТЬ
Как правило, когда вы переопределяете hashcode(...)
, вы также хотите переопределить equals(...)
. Так что для тех, кто будет или уже реализовал equals
, вот хороший справочник из моего Github...
@Override
public boolean equals(Object o) {
// Optimization (not required).
if (this == o) {
return true;
}
// Return false if the other object has the wrong type, interface, or is null.
if (!(o instanceof MyType)) {
return false;
}
MyType lhs = (MyType) o; // lhs means "left hand side"
// Primitive fields
return booleanField == lhs.booleanField
&& byteField == lhs.byteField
&& charField == lhs.charField
&& shortField == lhs.shortField
&& intField == lhs.intField
&& longField == lhs.longField
&& floatField == lhs.floatField
&& doubleField == lhs.doubleField
// Arrays
&& Arrays.equals(arrayField, lhs.arrayField)
// Objects
&& referenceField.equals(lhs.referenceField)
&& (nullableReferenceField == null
? lhs.nullableReferenceField == null
: nullableReferenceField.equals(lhs.nullableReferenceField));
}
Ответ 5
Сначала убедитесь, что equals реализовано правильно. Из статьи IBM developerWorks:
- Симметрия: для двух ссылок a и b, a.equals(b) тогда и только тогда, когда b.equals(a)
- Рефлексивность: для всех непустых ссылок a.equals(a)
- Транзитивность: если a.equals(b) и b.equals(c), то a.equals(c)
Затем убедитесь, что их отношение с hashCode соответствует контакту (из той же статьи):
- Согласованность с hashCode(): два равных объекта должны иметь одинаковое значение hashCode()
Наконец, хорошая хеш-функция должна стремиться к идеальной хэш-функции .
Ответ 6
about8.blogspot.com, вы сказали
Если equals() возвращает true для двух объектов, то hashCode() должен возвращать одно и то же значение. Если equals() возвращает false, то hashCode() должен возвращать разные значения
Я не могу согласиться с тобой. Если два объекта имеют один и тот же хэш-код, это не означает, что они равны.
Если A равно B, то A.hashcode должен быть равен B.hascode
но
если A.hashcode равен B.hascode, это не означает, что A должно равняться B
Ответ 7
Там хорошая реализация эффективной Java hashcode()
и equals()
логики в Apache Commons Lang. Оформить заказ HashCodeBuilder и EqualsBuilder.
Ответ 8
Если вы используете eclipse, вы можете сгенерировать equals()
и hashCode()
, используя:
Источник → Создать hashCode() и equals().
С помощью этой функции вы можете решить, какие поля вы хотите использовать для вычисления равенства и хеш-кода, а Eclipse генерирует соответствующие методы.
Ответ 9
Простое примечание для завершения другого более подробного ответа (в терминах кода):
Если я рассмотрю вопрос how-do-i-create-a-hash-table-in-java и особенно запись в jGuru FAQ, Я считаю, что некоторые другие критерии, по которым можно судить о хэш-коде, заключаются в следующем:
- синхронизация (поддерживает ли algo параллельный доступ или нет)?
- отказоустойчивая итерация (ли алгоритм обнаруживает коллекцию, которая изменяется во время итерации).
- значение null (поддерживает ли хэш-код нулевое значение в коллекции)
Ответ 10
Если я правильно понимаю ваш вопрос, у вас есть собственный класс коллекций (т.е. новый класс, который выходит из интерфейса Collection), и вы хотите реализовать метод hashCode().
Если ваш класс коллекции расширяет AbstractList, вам не о чем беспокоиться, уже существует реализация equals() и hashCode(), которая работает путем повторения всех объектов и добавления их hashCodes() вместе.
public int hashCode() {
int hashCode = 1;
Iterator i = iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
Теперь, если вы хотите, чтобы лучше всего вычислить хэш-код для определенного класса, я обычно использую оператор ^ (побитовый исключающий или) для обработки всех полей, которые я использую в методе equals:
public int hashCode(){
return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
Ответ 11
@about8: там есть довольно серьезная ошибка.
Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");
тот же хэш-код
вы, вероятно, хотите что-то вроде
public int hashCode() {
return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();
(можете ли вы получить hashCode непосредственно из int в Java в эти дни? Я думаю, что он делает некоторую автозагрузку. Если это так, пропустите toString, это уродливо.)
Ответ 12
Поскольку вы специально задавали коллекции, я хотел бы добавить аспект, который еще не упоминался в других ответах: HashMap не ожидает, что их ключи изменят свой хэш-код после их добавления в коллекцию. Уничтожьте всю цель...
Ответ 13
Используйте методы отражения в Apache Commons EqualsBuilder и HashCodeBuilder.
Ответ 14
любой метод хеширования, который равномерно распределяет хеш-значение в возможном диапазоне, является хорошей реализацией. См. Эффективную java (http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa=X&oi=book_result&resnum=1&ct=result), там есть хороший совет для реализации hashcode (пункт 9, я думаю...).
Ответ 15
Я предпочитаю использовать служебные методы из коллекции Google Collections lib из класса Объекты, которые помогают мне сохранить мой код в чистоте. Очень часто методы equals
и hashcode
создаются из шаблона IDE, поэтому их чтение не является чистым.
Ответ 16
Я использую крошечную оболочку вокруг Arrays.deepHashCode(...)
, потому что он правильно обрабатывает массивы, поставляемые как параметры
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
Ответ 17
Вот еще одна демонстрация подхода JDK 1.7+ с учетной записью суперкласса. Я считаю, что это довольно удобно с учетом класса hashCode(), чистой JDK-зависимости и дополнительной ручной работы. Обратите внимание, что Objects.hash()
имеет нулевую терпимость.
Я не включаю реализацию equals()
, но на самом деле вам это, конечно, понадобится.
import java.util.Objects;
public class Demo {
public static class A {
private final String param1;
public A(final String param1) {
this.param1 = param1;
}
@Override
public int hashCode() {
return Objects.hash(
super.hashCode(),
this.param1);
}
}
public static class B extends A {
private final String param2;
private final String param3;
public B(
final String param1,
final String param2,
final String param3) {
super(param1);
this.param2 = param2;
this.param3 = param3;
}
@Override
public final int hashCode() {
return Objects.hash(
super.hashCode(),
this.param2,
this.param3);
}
}
public static void main(String [] args) {
A a = new A("A");
B b = new B("A", "B", "C");
System.out.println("A: " + a.hashCode());
System.out.println("B: " + b.hashCode());
}
}
Ответ 18
Стандартная реализация слабая и ее использование приводит к ненужным конфликтам. Представь себе
class ListPair {
List<Integer> first;
List<Integer> second;
ListPair(List<Integer> first, List<Integer> second) {
this.first = first;
this.second = second;
}
public int hashCode() {
return Objects.hashCode(first, second);
}
...
}
Сейчас,
new ListPair(List.of(a), List.of(b, c))
а также
new ListPair(List.of(b), List.of(a, c))
иметь тот же hashCode
, а именно 31*(a+b) + c
что и множитель, используемый для List.hashCode
здесь повторно используется. Очевидно, что столкновения неизбежны, но создание ненужных столкновений просто... ненужно.
Там нет ничего существенно умного об использовании 31
. Множитель должен быть нечетным, чтобы избежать потери информации (любой четный множитель теряет по крайней мере самый старший бит, кратные четыре теряют два и т.д.). Любой нечетный множитель можно использовать. Маленькие множители могут привести к более быстрым вычислениям (JIT может использовать сдвиги и дополнения), но, учитывая, что умножение имеет задержку всего три цикла на современных Intel/AMD, это вряд ли имеет значение. Маленькие множители также приводят к большему столкновению для небольших входов, что иногда может быть проблемой.
Использовать простое число бессмысленно, поскольку простые числа не имеют смысла в кольце Z/(2 ** 32).
Поэтому я бы рекомендовал использовать случайно выбранное большое нечетное число (не стесняйтесь брать простое число). Поскольку процессоры i86/amd64 могут использовать более короткую инструкцию для подстановки операндов в один байт со знаком, то для множителей, подобных 109, есть небольшое преимущество в скорости. Для минимизации коллизий возьмите что-то вроде 0x58a54cf5.
Использование разных множителей в разных местах полезно, но, вероятно, недостаточно, чтобы оправдать дополнительную работу.
Ответ 19
При объединении хэш-значений я обычно использую метод объединения, который используется в библиотеке boost С++, а именно:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
Это довольно хорошая работа по обеспечению равномерного распределения. Для некоторого обсуждения того, как эта формула работает, см. Сообщение StackOverflow: Магическое число в boost:: hash_combine
Хорошее обсуждение различных хеш-функций: http://burtleburtle.net/bob/hash/doobs.html
Ответ 20
Для простого класса часто проще реализовать hashCode() на основе полей класса, которые проверяются реализацией equals().
public class Zam {
private String foo;
private String bar;
private String somethingElse;
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Zam otherObj = (Zam)obj;
if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
return true;
}
}
return false;
}
public int hashCode() {
return (getFoo() + getBar()).hashCode();
}
public String getFoo() {
return foo;
}
public String getBar() {
return bar;
}
}
Самое главное - сохранить hashCode() и equals() согласованно: if equals() возвращает true для двух объектов, hashCode() должен возвращать одно и то же значение. Если equals() возвращает false, то hashCode() должен возвращать разные значения.