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



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

683   20  

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 Machine) и мой собственный код на 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;

}

EDIT

как правило, при переопределении 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. equals (a)
  • транзитивность: если a. равно(b) и b.равно(c), то a.равно(c)

затем убедитесь, что их отношение с хэш-кодом уважает контакт (из той же статьи):

  • согласованность с hashCode (): два одинаковых объекта должны иметь одинаковое значение hashCode ()

наконец-то хорошая хэш-функция должна стремиться приблизиться к идеальная хэш-функция.

about8.blogspot.com - ты сказал

Если equals() возвращает true для двух объектов, то hashCode () должен возвращать то же значение. Если equals() возвращает false, то hashCode () должен возвращать разные значения

Я не могу согласиться с вами. Если два объекта имеют одинаковый хэш-код, это не значит, что они равны.

Если A равно B, то A. hashcode должен быть равен B. hascode

но

Если A. hashcode равен B. hascode это не значит, что A должен равняться B

есть хорошая реализация Эффективная Java ' s hashcode() и equals() логика Apache Commons Lang. Оформить заказ HashCodeBuilder и EqualsBuilder.

Если вы используете Eclipse, вы можете создать equals() и hashCode() использование:

Source - > Generate hashCode () and equals ().

С помощью этой функции вы можете решить поля вы хотите использовать для вычисления равенства и хэш-кода, и Eclipse генерирует соответствующие методы.

просто быстрая заметка для завершения другого более подробного ответа (в терминах кода):

Если я рассматриваю вопрос как-делать-Я-создание хэш-таблица-в-Ява и особенно jGuru статье, Я считаю, что некоторые другие критерии, по которым можно судить о хэш-коде:

  • синхронизация (поддерживает ли algo параллельный доступ или нет)?
  • fail safe итерация (algo обнаруживает коллекцию, которая изменяется во время итерация)
  • значение null (поддерживает ли хэш-код значение null в коллекции)

Если я правильно понял ваш вопрос, у вас есть пользовательский класс коллекции (т. е. новый класс, который простирается от интерфейса Collection) и вы хотите реализовать метод hashCode ().

Если ваш класс коллекции расширяет AbstractList, то вам не нужно беспокоиться об этом, уже есть реализация equals() и hashCode (), которая работает путем перебора всех объектов и добавления их хэш-кодов() вместе.

   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;
   }

@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 Collections lib from class Objects это помогает мне держать мой код в чистоте. Очень часто 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 () должен возвращать разные значения.

Comments

    Ничего не найдено.