ist es falsch, einen Hashcode eines Objekts als Summe, Multiplikation, was auch immer, von allen Klassenvariablen-Hashcodes zu definieren?

8

Sagen wir, ich habe die folgende Klasse:

%Vor%

Würde dies eine korrekte Implementierung von hashCode sein? Dies ist nicht, wie ich es normalerweise tue (ich neige dazu, Effective Javas Richtlinien zu folgen), aber ich habe immer die Versuchung, einfach so etwas wie den obigen Code zu machen.

Danke

    
devoured elysium 29.04.2010, 05:52
quelle

4 Antworten

12

Es hängt davon ab, was Sie mit "richtig" meinen. Angenommen, Sie verwenden hashCode() aller relevanten equals() -Definierungsfelder, dann ist es "korrekt". Solche Formeln werden jedoch wahrscheinlich keine gute Verteilung haben und daher wahrscheinlich mehr Kollisionen als sonst verursachen, was sich nachteilig auf die Leistung auswirken wird.

Hier ein Zitat von Effektive Java 2nd Edition , Punkt 9: Überschreiben Sie hashCode immer, wenn Sie equals

überschreiben
  

Während das Rezept in diesem Element relativ gute Hash-Funktionen liefert, liefert es keine modernen Hash-Funktionen, und auch Java-Plattform-Bibliotheken bieten keine solchen Hash-Funktionen wie zu Release 1.6. Das Schreiben solcher Hash-Funktionen ist ein Forschungsthema, das am besten den Mathematikern und Informatikern überlassen wird. [... Nichtsdestotrotz sollten die in diesem Artikel beschriebenen Techniken für die meisten Anwendungen geeignet sein.

Es kann nicht sehr viel Rechenleistung erfordern, um zu bewerten, wie gut Ihre vorgeschlagene Hash-Funktion ist, aber warum überhaupt stören? Warum nicht einfach etwas folgen, was sich in der Praxis als anständig erwiesen hat?

Josh Blochs Rezept

  • Speichern Sie in einer int -Variable mit dem Namen result .
  • einen konstanten Wert ungleich Null, sagen wir 17
  • Berechne einen int hashcode c für jedes Feld:
    • Wenn das Feld ein boolean ist, berechnen Sie (f ? 1 : 0)
    • Wenn das Feld ein byte, char, short, int ist, berechnen Sie (int) f
    • Wenn das Feld ein long ist, berechnen Sie (int) (f ^ (f >>> 32))
    • Wenn das Feld ein float ist, berechnen Sie Float.floatToIntBits(f)
    • Wenn das Feld ein double ist, berechne Double.doubleToLongBits(f) , dann hasse die resultierende long wie oben.
    • Wenn das Feld eine Objektreferenz ist und die Methode equals dieser Klasse das Feld durch rekursives Aufrufen von equals vergleicht, rufen Sie hashCode für das Feld rekursiv auf. Wenn der Wert des Felds null lautet, geben Sie 0 zurück.
    • Wenn das Feld ein Array ist, behandeln Sie es so, als ob jedes Element ein separates Feld wäre. Wenn jedes Element in einem Array-Feld signifikant ist, können Sie eine der in Version 1.5 hinzugefügten Arrays.hashCode -Methoden verwenden.
  • Kombiniere den Hashcode c in result wie folgt: result = 31 * result + c;

Nun, natürlich ist dieses Rezept ziemlich kompliziert, aber glücklicherweise müssen Sie es nicht jedes Mal neu implementieren, dank java.util.Arrays.hashCode(Object[]) (und com.google.common.base.Objects bietet eine praktische Vararg-Variante.

%Vor%

Siehe auch

  • Object.hashCode()
      

    Es ist nicht erforderlich, dass, wenn zwei Objekte ungleich der Methode equals(java.lang.Object) sind, der Aufruf der Methode hashCode für jedes der beiden Objekte eindeutige ganzzahlige Ergebnisse erzeugen muss. Allerdings sollte der Programmierer beachten, dass die Erzeugung von ganzzahligen Ergebnissen für ungleiche Objekte die Leistung von Hashtabellen verbessern kann.

polygenelubricants 29.04.2010, 06:03
quelle
2

Dies ist nach dem Vertrag zulässig. Aber es gibt immer 1 zurück. In HotSpot gibt es ein Kompilierzeit-Flag, um immer 1 für den Identity Hash-Wert zurückzugeben. Solche Entscheidungen werden jedoch zu einer schlechten Leistung führen.

Es gibt ein besonderes Problem mit der Multiplikation. Ein 0-Hash-Wert von einer Komponente wird nicht nur den Wert vernichten, sondern Potenzen von zwei werden die unteren Bits progressiv auf Null setzen.

Kommutative Operatoren haben das Problem, dass Neuordnungen von Werten einen Konflikt verursachen.

Wenn es eine bestimmte Beziehung zwischen Hash-Werten von Komponenten gibt, ist die Addition besonders schlecht. (4, 6) und (2, 8) kollidieren zum Beispiel.

    
Tom Hawtin - tackline 29.04.2010 06:06
quelle
1

Nein, aber in der Praxis ist das mit ziemlicher Sicherheit keine gute Idee. Am wichtigsten ist, dass Sie keines der Felder, die Sie im Hash-Code verwenden, ändern dürfen. Sie müssen alle konstant sein.

Wenn Sie einen ändern, kann das passieren: Sie fügen die Objectie in einem HashSet ein, Sie ändern ein Feld und testen dann, ob sich das Objekt im HashSet befindet. Obwohl es da drin ist, wird der Hash-Code aufgrund der Tatsache, dass der Hash-Code geändert wurde, nicht gefunden!

    
Carsten 29.04.2010 06:22
quelle
0

Es scheint mir so, dass Sie, wenn Sie nicht garantieren können, dass das Produkt eine Primzahl ist, eine Kollision (wenn auch wahrscheinlich selten) zwischen den resultierenden Hashcodes für ein Objekt bekommen können

    
TheSteve0 29.04.2010 05:54
quelle

Tags und Links