Сегодняшняя тема полностью соответствует теме прошлой недели. На этой неделе мы говорим о функции hashCode . Так же, как и функция equals , о которой мы говорили на прошлой неделе, у этого метода также есть контракт, которому следует следовать, хотя и более простой. Как и подпись, не принимает никаких параметров и возвращает целое число. Итак, давайте перейдем к контракту:
- При отсутствии изменений в объекте значение, возвращаемое функцией, должно оставаться неизменным.
- Если два объекта фактически равны, их хэш-коды должны быть одинаковыми.
- (Это скорее необязательное требование) Учитывая два объекта, которые фактически неравны, они не должны иметь разные значения хэш-кода.
Итак, как вы можете видеть, контракт hashcode очень привязан к равенству. Требование, которое нарушается чаще всего, – это второе требование. Если вы не реализуете функцию хэш-кода, два эффективно эквивалентных объекта, скорее всего, не будут иметь одинаковое значение, возвращаемое функцией. Итак, давайте рассмотрим пример:
Map addressBook = new HashMap<>();
addressBook.put(new Address("123","Foggy Lane" "Made Up City", "USA"), "James");
addressBook.get(new Address("123","Foggy Lane" "Made Up City", "USA"));
Учитывая приведенный выше пример, ожидалось бы, что третья строка вернет “James”, однако, если функция hashcode написана неправильно, вместо этого она вернет null .
Итак, давайте напишем простейшую функцию хэш-кода, которая является законной:
@Override
public int hashCode() {
return 42;
}
Да, это полностью допустимая функция хэш-кода в соответствии с контрактом. Он всегда возвращает одно и то же, если вызывается несколько раз для одного и того же объекта, он возвращает одно и то же значение для функционально эквивалентных объектов, и это нормально, что он возвращает один и тот же хэш-код для двух объектов, которые не являются функционально эквивалентными. Однако, несмотря на то, что это соответствует контракту, отсутствие некоторого разнообразия возвращаемых значений является ужасной идеей и может привести к значительному снижению производительности. Например, приведенная выше функция хэш-кода эффективно превратит хэш-карту в связанный список с точки зрения производительности.
Должен быть лучший способ, и он есть. Эффективная Java дает нам рецепт, которому мы можем следовать, чтобы создать надежную функцию хэш-кода.
- Объявите
intnamedresultи инициализируйте его значением первого значимого поля объекта (напоминание о том, что означает “значимое поле”, – это поле, которое принимает участие в принятии решения, если два объекта эквивалентны), как вычислено на шаге 2. - Для каждого оставшегося значимого поля выполните следующие действия
- Вычислить
intхэш-код для поля - Если поле является примитивом, вычислите его с помощью функции
hashCodeкоробочной версии. Пример:Double.hashCode(значение) - Если поле является ссылкой на объект, вызовите функцию
hashCodeэтого объекта. Для null используйте значение0 - Если это массив, обрабатывайте каждый значимый элемент как отдельное поле или используйте
Arrays.hashCodeесли все они значимы. - Объедините только что вычисленный хэш-код с результатом следующим образом:
результат * результат + Хэш-код нового поля
- Вычислить
- Возврат
результат
Если вы правильно следуете приведенному выше алгоритму, у вас должен быть рассчитан надежный хэш-код. Приятными элементами этого алгоритма являются то, что порядок операций имеет значение и, следовательно, приводит к лучшему распределению. Умножение на 31 приятно, потому что это нечетное простое число. Нечетное помогает при переполнении целых чисел, а простая часть – это просто потому, что простые числа – это круто. Во всей реальности это звучит просто так, как будто стало стандартом. Так что давайте посмотрим наш новый Функция адреса хэш-кода.
@Override
public int hashCode() {
int result = streetAddress.hashCode();
result = 31 * result + road.hashCode();
result = 31 * result + country.hashCode();
return result;
}
Довольно простой, но эффективный. Существуют ли более простые способы написания этих функций? Конечно. Одним из примеров является использование Objects.hashCode(значимое поле 1, значениеfield2, ...) . Это хорошо тем, что это однострочная функция hashCode . Недостатком этого является то, что производительность is хуже, чем в нашем предыдущем примере. Но на самом деле, на мой взгляд, лучше всего использовать что-то вроде Ломбока. Ломбок включает это с помощью аннотации @EqualsAndHashCode . Это здорово, что разработчики Lombok заставляют генерировать equals и Хэш-код вместе.
Наконец, давайте просто рассмотрим некоторые вещи, которые следует иметь в виду с помощью hashcode :
- Всегда переопределяйте
хэш-кодпри переопределенииравняется - Включите все значения, используемые в
equals, как часть вычисленияhashcode. - Не делитесь за пределами функции тем, как вычисляется хэш-код, это излишне может привязать вас к реализации с подпараметром.
Оригинал: “https://dev.to/kylec32/effective-java-tuesday-obey-the-hashcode-contract-3onl”