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

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

с Java 7+, я думаю, Objects.hashCode(collection) должен быть идеальным решением!

Diablo 17.03.2016 14:45

@Diablo Я не думаю, что это вообще ответ на вопрос - этот метод просто возвращает collection.hashCode() (hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/sha‌ re /…)

cbreezier 19.04.2016 08:35
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
306
2
247 953
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 (): если equals () возвращает true для двух объектов, то hashCode () должен возвращать то же значение. Если equals () возвращает false, то hashCode () должен возвращать другие значения.

Вроде SquareCog уже заметили. Если хэш-код генерируется один раз из конкатенации двух строк, чрезвычайно легко генерировать массу коллизий: ("abc"+""= = "ab"+"c"= = "a"+"bc"= = ""+"abc"). Это серьезный недостаток. Было бы лучше оценить хэш-код для обоих полей, а затем вычислить их линейную комбинацию (желательно с использованием простых чисел в качестве коэффициентов).

Krzysztof Jabłoński 30.04.2013 10:34

@ KrzysztofJabłoński Верно. Более того, замена foo и bar также приводит к ненужному столкновению.

maaartinus 10.12.2017 22:25

@ 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, это некрасиво.)

ошибка содержится в длинном ответе about8.blogspot.com - получение хэш-кода из конкатенации строк оставляет вам хеш-функцию, которая одинакова для любой комбинации строк, составляющих одну и ту же строку.

SquareCog 22.09.2008 17:53

Значит, это мета-обсуждение и никак не связано с вопросом? ;-)

Huppie 22.09.2008 21:40

Это исправление предложенного ответа, имеющее довольно существенный недостаток.

SquareCog 23.09.2008 02:13

Это очень ограниченная реализация

Christopher Rucinski 15.09.2015 14:58

Ваша реализация избегает проблемы и вводит другую; Замена foo и bar приводит к тому же hashCode. Ваш toString AFAIK не компилируется, а если и компилируется, то это ужасно неэффективно. Что-то вроде 109 * getFoo().hashCode() + 57 * getBar().hashCode() быстрее, проще и не вызывает ненужных коллизий.

maaartinus 10.12.2017 22:18

Сначала убедитесь, что равенство реализовано правильно. От статья IBM DeveloperWorks:

  • Symmetry: For two references, a and b, a.equals(b) if and only if b.equals(a)
  • Reflexivity: For all non-null references, a.equals(a)
  • Transitivity: If a.equals(b) and b.equals(c), then a.equals(c)

Затем убедитесь, что их связь с hashCode уважает контакт (из той же статьи):

  • Consistency with hashCode(): Two equal objects must have the same hashCode() value

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

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

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

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

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

Если я правильно понимаю ваш вопрос, у вас есть собственный класс коллекции (т.е. новый класс, который расширяется от интерфейса 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);
}

любой метод хеширования, который равномерно распределяет хеш-значение по возможному диапазону, является хорошей реализацией. См. Эффективную java (http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=ru&sa=X&ult&result=news&result&result&result=ru&sa=X&result&result&result=news), там есть хороший совет для реализации хэш-кода (пункт 9, я думаю ...).

Ответ принят как подходящий

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

A почти для всех случаев разумная хорошая реализация была предложена в Эффективная 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

Это должно привести к правильному распределению хеш-значений для большинства ситуаций использования.

Интересный ответ. Есть ли ссылка на математическое доказательство того, почему эта формула работает?

runaros 22.09.2008 12:50

Да, мне особенно любопытно, откуда взялось число 37.

Kip 22.09.2008 21:25

Я не знаю никаких доказательств. Число 37 произвольно, но должно быть простым. Почему? Я не совсем уверен, но это связано с артритом по модулю и свойствами простых чисел, которые приводят к распределению.

dmeister 23.09.2008 03:55

Спасибо за подробный ответ. Не могли бы вы рассказать мне, какой справочник / руководство вы использовали для создания этого набора советов? Также мне интересно, зачем умножать на 37? В чем магия в 37? Почему умножение лучше сдвига вправо (result = result << 16 + c)?

dma_k 04.10.2010 17:57

Я использовал пункт 8 книги Джоша Блоха «Эффективная Java».

dmeister 04.10.2010 18:39

@dma_k Причина использования простых чисел и метода, описанного в этом ответе, состоит в том, чтобы гарантировать, что вычисленный хэш-код будет уникальным. При использовании непростых чисел вы не можете этого гарантировать. Неважно, какое простое число вы выберете, в числе 37 нет ничего волшебного (жаль, что 42 не простое число, а?)

Simon Forsberg 15.02.2013 17:58

@ SimonAndréForsberg Ну, вычисленный хэш-код не всегда может быть уникальным :) Это хэш-код. Однако у меня возникла идея: у простого числа только один множитель, а у непростого - минимум два. Это создает дополнительную комбинацию для оператора умножения, чтобы получить тот же хэш, то есть вызвать коллизию.

dma_k 15.02.2013 18:08

@dma_k Да, это то, что я имел в виду :) Конечно, он не может быть уникальным все время (в противном случае для определения .equals было бы достаточно сравнения hashCode), использование простых чисел просто уменьшает коллизии.

Simon Forsberg 15.02.2013 19:46

Есть ли конкретная причина для сопоставления true с 0 и false с 1, а не наоборот?

afsantos 18.03.2013 16:01

@afsantos: Я ничего не могу придумать. Код из книги - это скорее общее руководство как закон и ни в коем случае не единственный правильный способ сделать это.

dmeister 18.03.2013 18:45

Я отредактировал сообщение, но это минимальное количество символов действительно раздражает при внесении незначительных изменений в разметку :( Если принято, может ли кто-нибудь удалить EDITED рядом с пунктом 1? Спасибо.

Timo 12.11.2013 12:07

+1 Отличное объяснение, и мне очень нравится, что вы предоставили источник (то есть книгу Блоха, а не исходный код) на случай, если кому-то понадобится дополнительная информация.

jwir3 28.01.2014 01:07

Если поле f является массивом, у вас также есть возможность использовать методы java.util.Arrays.hashCode для вычисления хэш-значения этого поля.

Brigham 26.08.2014 21:59

Что, если поле является строкой? Это определенно объект, но какой должна быть реализация хэш-кода hte

inquisitive 21.09.2015 08:36

Как рассчитать в случае объекта типа String Member

Pankaj 12.02.2016 14:58

Одна из основных целей хеш-функции - сделать ее недорогой в оценке. Я просто написал хеш-функцию для структуры данных формальных параметров в компиляторе. Тест на равенство, конечно, должен проверять каждое имя и тип параметра после обеспечения структурной изометрии. Но что касается «Для каждого поля f, проверенного в методе equals ()», можно было бы найти хэш-поиск почти таким же дорогим, как линейный поиск. Вам нужно только создать тривиальную хеш-функцию, которая дает широкое распределение результатов по шаблонам использования. Помогают эмпирические измерения и эксперименты, а не предположения.

Cope 04.03.2016 14:43

@Cope: Я не согласен с тем, что основная цель хеш-функции - скорость. Хотя есть определенные особые случаи для других реализаций hashCode, это очень хорошее начало. Безусловно, это правильная реализация. Большинство «умных» просто ошибаются.

dmeister 05.03.2016 03:21

Hej @dmeister - Я стою на своем. Все очень просто. 1. Основная цель хэш-функции (в структурах данных, а не, например, в шифровании) - скорость, чтобы избежать затрат на линейный поиск. 2. Существует компромисс между стоимостью вычисления хэша и стоимостью линейного поиска, и в многополевых структурах даже простого поиска по правильному ключу может быть достаточно, чтобы адекватно снизить стоимость. 3. Для больших структур данных (таких как мои формальные параметры) количество вычислений будет исчисляться десятками, если вы измеряете «каждое поле». 4. Универсального ответа не существует - значит, надо мерить.

Cope 06.03.2016 12:20

Я видел, что 37 в другом месте для вычисления алгоритма хэш-кода строки и решил, что есть 26 букв и 10 цифр плюс символ пробела, что дает 37 lol

sotn 13.03.2016 20:19

Не мог бы кто-нибудь объяснить, что делает эта строка в случае длинного? е ^ (е >>> 32)

Abhishek Singh 17.01.2018 07:42

В третьей версии Effective Java содержимое Пункт 8 не имеет ничего общего с хэш-кодом, поэтому, возможно, удалите упоминание об элементе.

John R Perry 25.03.2018 06:43

Я думаю, что пункт 8 основан на втором издании. Третье издание имеет страницу в конце, где различные элементы соотносятся от выпуска к выпуску.

dmeister 02.04.2018 19:19

При хорошем хешировании вероятность хеш-коллизии должна быть 2 ^ -32, но при 37 вы можете легко столкнуться с коллизиями. Например, для объекта с двумя int (x, y) все хэш-коды для (1, -37), (0, 0), (-1, 37) будут иметь значение 0. Так что в реальной жизни (с маленькими значения гораздо более вероятны, чем большие), вероятность далека от 2 ^ -32. Лучше использовать простое число, например 912376189.

kristjan 06.06.2018 16:49

Эта функция обрабатывает double, как .Net, и мое недавнее тестирование показывает, что это очень плохо, когда ваши двойники не являются действительно случайными, а действительно целыми числами или имеют только один или два десятичных знака. Функция повторного хеширования из HashMap помогает, но все же не так хороша, как простое использование HashCode для int в этом случае.

NetMage 17.05.2019 03:37

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

Обратной стороной этого API является то, что вы оплачиваете стоимость создания объекта каждый раз, когда вызываете equals и hashcode (если только ваш объект не является неизменяемым и вы предварительно вычисляете хэш), что в некоторых случаях может быть очень много.

James McMahon 04.02.2012 05:35

до недавнего времени это был мой любимый подход. Я столкнулся с StackOverFlowError при использовании критерия для ассоциации SharedKey OneToOne. Более того, класс Objects предоставляет методы hash(Object ..args) и equals() начиная с Java7. Они рекомендуются для любых приложений, использующих jdk 1.7+.

Diablo 17.03.2016 14:26

@Diablo Я думаю, ваша проблема заключалась в цикле в графе объектов, а затем вам не повезло с большей частью реализации, поскольку вам нужно игнорировать некоторую ссылку или прервать цикл (требуя IdentityHashMap). FWIW Я использую хэш-код на основе идентификатора и равенство для всех сущностей.

maaartinus 10.12.2017 22:12

about8.blogspot.com, вы сказали

if equals() returns true for two objects, then hashCode() should return the same value. If equals() returns false, then hashCode() should return different values

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

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

но

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

Если (A != B) and (A.hashcode() == B.hashcode()), это то, что мы называем конфликтом хэш-функции. Это потому, что codomain хэш-функции всегда конечен, а его домен обычно нет. Чем больше кодомен, тем реже должно происходить столкновение. Хорошая хеш-функция должна возвращать разные хеши для разных объектов с наибольшей вероятностью, достижимой при конкретном размере кодомена. Однако это редко может быть полностью гарантировано.

Krzysztof Jabłoński 29.04.2013 12:45

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

Christopher Rucinski 15.09.2015 14:55

Хорошие комментарии, но будьте осторожны при использовании термина «разные объекты» ... потому что equals () и, следовательно, реализация hashCode () не обязательно относятся к разным объектам в объектно-ориентированном контексте, но обычно больше относятся к их представлениям модели предметной области (например, два люди могут считаться одинаковыми, если у них общий код страны и идентификатор страны - хотя это могут быть два разных «объекта» в JVM - они считаются «равными» и имеют заданный хэш-код) ...

Darrell Teague 03.06.2016 22:04

Я предпочитаю использовать служебные методы из Библиотека Google Collections из класса Objects, которые помогают мне поддерживать мой код в чистоте. Очень часто методы equals и hashcode создаются из шаблона IDE, поэтому их трудно читать.

Используйте методы отражения в Apache Commons EqualsBuilder и HashCodeBuilder.

Если вы собираетесь использовать это, имейте в виду, что отражение стоит дорого. Честно говоря, я бы не стал использовать это ни для чего, кроме как выбросить код.

James McMahon 04.02.2012 05:31

Если вы используете eclipse, вы можете сгенерировать equals() и hashCode(), используя:

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

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

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

+1 Хорошее практическое решение. Решение dmeister более комплексное, но я часто забываю обрабатывать нули, когда пытаюсь написать хэш-коды самостоятельно.

Quantum7 07.04.2011 04:31

+1 Согласен с Quantum7, но я бы сказал, что также очень хорошо понимать, что делает реализация, созданная Eclipse, и откуда она берет детали своей реализации.

jwir3 28.01.2014 01:05

Извините, но ответы, касающиеся «функциональности, предоставляемой [некоторой IDE]», на самом деле не актуальны в контексте языка программирования в целом. Существуют десятки IDE, и это не отвечает на вопрос ... а именно потому, что это больше связано с алгоритмическим определением и напрямую связано с реализацией equals () - то, о чем IDE ничего не знает.

Darrell Teague 03.06.2016 21:59

При объединении хеш-значений я обычно использую метод объединения, который используется в библиотеке boost C++, а именно:

seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);

Это достаточно хорошо для обеспечения равномерного распределения. Некоторое обсуждение того, как работает эта формула, можно найти в сообщении StackOverflow: Магическое число в бусте :: hash_combine

Хорошее обсуждение различных хеш-функций: http://burtleburtle.net/bob/hash/doobs.html

Это вопрос о Java, а не о C++.

dano 10.10.2017 22:52

Если вас устраивает эффективная реализация 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), но работает одинаково.

Если у кого-то нет веской причины не использовать их, нужно обязательно использовать их в любом случае. (Формулируя это сильнее, поскольку это должно быть сформулировано ИМХО.) Применяются типичные аргументы в пользу использования стандартных реализаций / библиотек (лучшие практики, хорошо протестированные, менее подверженные ошибкам и т.д.).

Kissaki 28.01.2014 17:23

Случай, чтобы не использовать реализацию библиотеки, - это когда вы реализовали собственный метод Object # equals (Object obj). См. JavaDoc Object # hashcode (): «Если два объекта равны в соответствии с методом equals (Object), то вызов метода hashCode для каждого из двух объектов должен привести к одинаковому целочисленному результату». Использование любой из этих реализаций библиотеки сгенерирует хэш-код «псевдо» адреса памяти, который может нарушить описанное требование.

justin.hughey 10.03.2014 20:35

@ justin.hughey вы, кажется, запутались. Единственный случай, когда вы должны переопределить hashCode, - это если у вас есть собственный equals, и именно для этого предназначены эти библиотечные методы. В документации довольно четко описано их поведение по отношению к equals. Реализация библиотеки не претендует на то, чтобы вы не знали, каковы характеристики правильной реализации hashCode - эти библиотеки делают Полегче для вас, чтобы реализовать такую ​​соответствующую реализацию в большинстве случаев, когда equals переопределяется.

bacar 11.03.2014 04:06

Ты совершенно прав. Спасибо, что указали на это, я не знаю, откуда я пришел по этому поводу. :)

justin.hughey 11.03.2014 16:42

Для всех разработчиков Android, которые смотрят на класс java.util.Objects, он был представлен только в API 19, поэтому убедитесь, что вы работаете на KitKat или более поздней версии, иначе вы получите NoClassDefFoundError.

Andrew Kelly 05.02.2015 08:30

Лучший ответ IMO, хотя в качестве примера я бы предпочел метод JDK7 java.util.Objects.hash(...), чем метод guava com.google.common.base.Objects.hashCode(...). Я думаю, что большинство людей предпочли бы стандартную библиотеку дополнительной зависимости.

Malte Skoruppa 04.11.2015 22:00

Если есть два или более аргумента и если какой-либо из них является массивом, результат может быть не таким, как вы ожидаете, потому что hashCode() для массива - это просто его java.lang.System.identityHashCode(...).

starikoff 21.12.2015 14:48

@starikoff Да, вспомогательные методы не освобождают вас от необходимости понимать, что вы делаете. Они просто помогают избежать определенных ошибок и дублирования кода.

bacar 21.12.2015 16:31

@bacar Конечно. Я просто оставил свой комментарий, чтобы тот, кто узнает об этих методах из этого поста, также узнал об их ограничениях.

starikoff 21.12.2015 17:17

Комментарии об ассоциации переопределения equals () верны. hashCode () не реализуется в вакууме и должен быть напрямую связан с тем, как equals () реализован в документации (два объекта могут иметь один и тот же hashCode, но НЕ быть равными - но два равных объекта ДОЛЖНЫ иметь одинаковый hashCode).

Darrell Teague 03.06.2016 22:01

@Kissaki Напротив: используя стандартную реализацию приводит к стандартным коллизиям.

maaartinus 10.12.2017 21:03

@AndrewKelly Прошло несколько лет, но для всех, кто сталкивался с этим и все еще поддерживает <19, теперь есть ObjectsCompat.hash(...), и его реализация - это просто Arrays.hashCode(...) для API <19, что похоже на ту же реализацию в Objects.hash(...). В Android Studio есть генератор кода для этого, который вставит Objects.hash(...), хотя независимо от того, какой у вас minSdkVersion, поэтому разработчики должны знать, что они могут просто заменить его на ObjectsCompat, и все будет в порядке.

mkuech 28.03.2020 00:55

Objects.hashCode () - это один из API-интерфейсов, который теперь автоматически удаляется D8 в Android Studio 4+, поэтому вы можете использовать исходный API для целей, предшествующих API 19 (нет необходимости в ObjectsCompat). Полный список API-интерфейсов с обессахиванием см. В этом файле. jakewharton.com/static/files/d8_api_desugar_list.txt

Andrew Kelly 30.03.2020 01:17

Хотя это связано с Документация 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;

}

РЕДАКТИРОВАТЬ

Обычно, когда вы переопределяете 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));
}

Документация Android теперь больше не включает приведенный выше код, поэтому вот кешированная версия из Wayback Machine - документация для Android (7 февраля 2015 г.)

Christopher Rucinski 18.07.2017 12:14

Я использую крошечную оболочку вокруг Arrays.deepHashCode(...), потому что она правильно обрабатывает массивы, предоставленные в качестве параметров

public static int hash(final Object... objects) {
    return Arrays.deepHashCode(objects);
}

Вот еще одна демонстрация подхода 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());
    }

}

Стандартная реализация слабая, и ее использование приводит к ненужным конфликтам. Представьте себе

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.

Использование разных множителей в разных местах полезно, но, вероятно, недостаточно, чтобы оправдать дополнительную работу.

Другие вопросы по теме