Рубрики
Без рубрики

Вступающий в силу Java Вторник! Подчиняйтесь контракту “Хэш-код”

Погружение в одиннадцатую главу “Эффективная Java”. Помеченный как java, эффективный, хэш-код, архитектура.

Сегодняшняя тема полностью соответствует теме прошлой недели. На этой неделе мы говорим о функции 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 дает нам рецепт, которому мы можем следовать, чтобы создать надежную функцию хэш-кода.

  1. Объявите int named result и инициализируйте его значением первого значимого поля объекта (напоминание о том, что означает “значимое поле”, – это поле, которое принимает участие в принятии решения, если два объекта эквивалентны), как вычислено на шаге 2.
  2. Для каждого оставшегося значимого поля выполните следующие действия
    • Вычислить int хэш-код для поля
    • Если поле является примитивом, вычислите его с помощью функции hashCode коробочной версии. Пример: Double.hashCode(значение)
    • Если поле является ссылкой на объект, вызовите функцию hashCode этого объекта. Для null используйте значение 0
    • Если это массив, обрабатывайте каждый значимый элемент как отдельное поле или используйте Arrays.hashCode если все они значимы.
    • Объедините только что вычисленный хэш-код с результатом следующим образом: результат * результат + Хэш-код нового поля
  3. Возврат результат

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