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