Лучшая реализация для метода hashCode для коллекции

Как мы выбираем лучшую реализацию hashCode() метод для коллекции (при условии, что метод equals был правильно переопределен)?

20 ответов


лучшая реализация? Это сложный вопрос, потому что он зависит от шаблона использования.

A почти во всех случаях разумная хорошая реализация была предложена в Джош Блох ' s Эффективная Java в пункте 8 (второе издание). Лучше всего посмотреть его там, потому что автор объясняет, почему подход хорош.

короткая версия

  1. создать int result и назначить ненулевой значение.

  2. на поле 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 является массив: увидеть каждое поле как отдельный элемент и вычислить значение хэша в рекурсивные моды и объединить значения как описано следующий.
  3. объединить значение хэша c С result:

    result = 37 * result + c
    
  4. возвращение 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 () должен возвращать разные значения.