Лучшая реализация для метода hashCode для коллекции
20 ответов
лучшая реализация? Это сложный вопрос, потому что он зависит от шаблона использования.
A почти во всех случаях разумная хорошая реализация была предложена в Джош Блох ' s Эффективная Java в пункте 8 (второе издание). Лучше всего посмотреть его там, потому что автор объясняет, почему подход хорош.
короткая версия
создать
int result
и назначить ненулевой значение.-
на поле
f
проверен вequals()
метод, вычислить хэш-кодc
by:- если поле 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
это должно привести к правильному распределению хэш-значений для большинства ситуаций использования.
Если вы довольны эффективной реализацией Java, рекомендованной dmeister, вы можете использовать вызов библиотеки вместо сворачивания собственного:
@Override
public int hashCode(){
return Objects.hashCode(this.firstName, this.lastName);
}
для этого требуется либо гуава (com.google.common.base.Objects.hashCode(...)
) или JDK7 (java.util.Objects.hash(...)
), но работает одинаково.
лучше использовать функциональность, предоставляемую Eclipse, которая делает довольно хорошую работу, и вы можете приложить свои усилия и энергию для разработки бизнес-логики.
хотя это связано с Android
документация (машина Wayback) и мой собственный код на Github, он будет работать для Java в целом. Мой ответ является продолжением dmeister это только с кодом, который намного проще читать и понимать.
@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));
}
сначала убедитесь, что equals реализован правильно. От статья IBM DeveloperWorks:
- симметрия: для двух ссылок, a и b, a.равно(b) тогда и только тогда, когда b.равно (a)
- рефлексивность: для всех ненулевых ссылок a.равно (a)
- транзитивность: если a.равно(b) и b.равно(c), затем a.равно(c)
затем убедитесь, что их отношение с hashCode уважает контакт (из той же статьи):
- согласованность с hashCode (): два равных объекта должны иметь одинаковое значение hashCode ()
наконец-то хорошая хэш-функция должна стремиться приблизиться к идеальная хэш-функция.
about8.blogspot.com-ты сказал
Если equals() возвращает true для двух объектов, то hashCode () должен возвращать одно и то же значение. Если equals() возвращает false, то hashCode () должен возвращать разные значения
Я не могу согласиться с вами. Если два объекта имеют один и тот же хэш-код, это не означает, что они равны.
Если A равно B, то A. хэш-код должен быть равен B. hascode
но
Если A. хэш-код равен B. hascode это не означает, что A должен равняться B
есть хорошая реализация Эффективная Java ' s hashcode()
и equals()
логика Apache Commons Lang. Оформить заказ HashCodeBuilder и EqualsBuilder.
Если вы используете Eclipse, вы можете создать equals()
и hashCode()
использование:
Source - > Generate hashCode() и equals().
используя эту функцию, вы можете решить поля вы хотите использовать для вычисления равенства и хэш-кода, а Eclipse генерирует соответствующие методы.
просто краткое примечание для завершения другого более подробного ответа (в терминах кода):
Если я рассмотрю вопрос how-do-i-create-a-hash-table-in-java и особенно jGuru статье, Я считаю, что некоторые другие критерии, по которым можно судить о хэш-коде:
- синхронизация (поддерживает ли Algo параллельный доступ или нет) ?
- fail safe итерация (Algo обнаруживает коллекцию, которая изменяется во время итерация)
- значение null (поддерживает ли хэш-код значение null в коллекции)
Если я правильно понял ваш вопрос, у вас есть пользовательский класс коллекции (т. е. новый класс, который простирается от интерфейса 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);
}
@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();
(можете ли вы получить хэш-код непосредственно из int в Java в эти дни? Я думаю, что он делает автокастинг.. если это так, пропустите toString, это уродливо.)
поскольку вы специально попросили коллекции, я хотел бы добавить аспект, который другие ответы еще не упоминали: HashMap не ожидает, что их ключи изменят свой хэш-код после их добавления в коллекцию. Это разрушит всю цель...
используйте методы отражения на Apache Commons EqualsBuilder и HashCodeBuilder.
любой метод хэширования, который равномерно распределяет хэш-значение по возможному диапазону, является хорошей реализацией. См. эффективный 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), там есть хороший совет для реализации хэш-кода (пункт 9, я думаю...).
Я предпочитаю использовать методы утилиты fromm Google коллекции lib из объектов класса это помогает мне держать мой код в чистоте. Очень часто equals
и hashcode
методы сделаны из шаблона IDE, поэтому их нельзя прочитать.
Я использую крошечную обертку вокруг Arrays.deepHashCode(...)
потому что он правильно обрабатывает массивы, поставляемые в качестве параметров
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
вот еще одна демонстрация подхода JDK 1.7+ с учетом логики суперкласса. Я считаю это довольно удобным с учетом класса объектов hashCode (), чистой зависимостью JDK и без дополнительной ручной работы. Пожалуйста, обратите внимание Objects.hash()
имеет значение null терпимо.
Я не включаю никаких 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());
}
}
стандартная реализация слаба, и ее использование приводит к ненужным коллизиям. Представьте себе
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.
использование разных множителей в разных местах полезно, но, вероятно, недостаточно, чтобы оправдать дополнительную работу.
при объединении хэш-значений я обычно использую метод объединения, используемый в библиотеке boost c++, а именно:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
это делает довольно хорошую работу по обеспечению равномерного распределения. Для некоторого обсуждения того, как эта формула работает, см. сообщение StackOverflow: магическое число в boost:: hash_combine
есть хорошее обсуждение различных хэш-функций: http://burtleburtle.net/bob/hash/doobs.html
для простого класса часто проще всего реализовать 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 (): если equals() возвращает true для двух объектов, то hashCode() должен возвращать одно и то же значение. Если equals() возвращает false, то hashCode () должен возвращать разные значения.