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

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

Ответ 1

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

Практически во всех случаях разумная хорошая реализация была предложена в Josh Bloch Effective Java в пункте 8 (второе издание). Лучше всего искать это там, потому что автор объясняет, почему подход хорош.

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

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

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

    result = 37 * result + c
    
  4. Вернуть 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

Ответ 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 не ожидает, что их ключи изменят свой хэш-код после их добавления в коллекцию. Уничтожьте всю цель...

Ответ 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() должен возвращать разные значения.