L'implementazione standard è debole e il suo utilizzo porta a collisioni non necessarie. Immagina a
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);
}
...
}
Adesso,
new ListPair(List.of(a), List.of(b, c))
e
new ListPair(List.of(b), List.of(a, c))
hanno lo stesso hashCode
, vale 31*(a+b) + c
a dire il moltiplicatore utilizzato perList.hashCode
viene riutilizzato qui. Ovviamente, le collisioni sono inevitabili, ma produrre collisioni inutili è solo ... inutile.
Non c'è nulla di sostanzialmente intelligente nell'uso 31
. Il moltiplicatore deve essere dispari per evitare di perdere informazioni (qualsiasi moltiplicatore pari perde almeno il bit più significativo, i multipli di quattro ne perdono due, ecc.). È possibile utilizzare qualsiasi moltiplicatore dispari. I piccoli moltiplicatori possono portare a un calcolo più rapido (la JIT può utilizzare turni e aggiunte), ma dato che la moltiplicazione ha una latenza di soli tre cicli sui moderni Intel / AMD, questo non ha importanza. I piccoli moltiplicatori portano anche a una maggiore collisione per piccoli input, che a volte può essere un problema.
L'uso di un numero primo è inutile poiché i numeri primi non hanno alcun significato nell'anello Z / (2 ** 32).
Quindi, consiglierei di usare un grande numero dispari scelto casualmente (sentiti libero di prendere un numero primo). Poiché le CPU i86 / amd64 possono utilizzare un'istruzione più breve per gli operandi che si adattano a un singolo byte con segno, esiste un vantaggio di velocità minuscola per moltiplicatori come 109. Per ridurre al minimo le collisioni, prendere qualcosa come 0x58a54cf5.
L'uso di moltiplicatori diversi in luoghi diversi è utile, ma probabilmente non è sufficiente per giustificare il lavoro aggiuntivo.
Objects.hashCode(collection)
dovrebbe essere una soluzione perfetta!