hashCode() bei Klassen, die nicht immutable sind

cmrudolph

Gesperrter Benutzer
Hi,

ich habe eine Frage zur Erstellung von hashCode() Methoden.
Michael Inden behauptet in "Der Weg zum Java-Profi", dass eine hashCode() Methode nur die unveränderlichen Felder eines Objektes in die Berechnung mit einbeziehen darf. Die Argumentation klingt für mich schlüssig:
Wenn man auch veränderliche Felder mit einbezieht, dann findet man dieses Objekt in einer HashMap nicht wieder, sobald sich eines dieser Felder ändert.

Joshua Bloch hingegen schreibt in "Effective Java", dass alle Felder, die bei dem Vergleich in equals() relevant sind mit einbezogen werden müssen. Auch hierfür finde ich zumindest ein logisches Argument:
Wie soll ich ansonsten einen Hash für eine Klasse berechnen, die ausschließlich veränderbare Felder besitzt? Da könnte ich ansonsten nur eine Konstante zurückgeben, was bekanntlicherweise die schlechteste aller gültigen hashCode() Implementierungen darstellt.

Kann mir jemand aus diesem Dilemma helfen?
 
Equals bezeichnet die fachliche Gleichheit. Diese definierst Du selbst. Wenn Du bspw. bei einer Klasse Punktestand definierst, dass zwei Instanzen gleich sein sollen, wenn die in ihnen gespeicherte Punktezahl gleich ist, dann muss diese bei equals() mit einbezogen werden. Equals und hashCode müssen konsistent sein, also musst Du die Punktezahl auch hier einbeziehen. Wenn sich die Punktezahl im Laufe eines Spiels ändert, hast Du ein veränderliches Feld bei equals() und hashCode() einbezogen. Das ist zunächst völlig valide. Bspw. bei Sets führt das aber -wie schon von Dir beschrieben- zu Problemen, aus denen es zwei mögliche Auswege gibt:
- Hole die zu verändernde Instanz zunächst aus dem Set raus, änder die Werte und speichere sie wieder
- Mache die Klasse immutable (keine setter und Felder ggf. final) und bilde die Veränderung mit Kopien ab. Auch hier musst Du die zu verändernde Instanz aus dem Set löschen, um Datenmüll abzuräumen.
 
Hmm... Wozu equals() und hashCode() da sind, ist mir ja schon bewusst. Ich kann mich nur nicht entscheiden, ob es "richtig" oder "falsch" ist, wenn man veränderliche Felder beim hashCode() mit einbezieht.

Wenn man aber ganz logisch an die Sache herangeht, dann folgt folgendes:
- in hashCode() dürfen keine Felder geprüft werden, die nicht auch bei equals() geprüft werden. Ansonsten wird der Kontrakt verletzt, wenn sich zwei Objekte nur in diesem Feld unterscheiden (a.equals(b) == true und a.hashCode() != b.hashCode() verletzt die Bedingung a.equals(b) => a.hashCode() == b.hashCode()).
- der Kontrakt wird nicht verletzt, wenn man beim Berechnen des Hashes nicht alle in equals() geprüften Felder mit einbezieht. Dann haben eben zwei Objekte den gleichen Hash, obwohl sie verschieden sind. Das widerspricht dem Kontrakt nicht.

Aus diesen beiden Schlüssen kann man dann wohl folgern, dass bei Klassen, die ausschließlich veränderliche Felder haben entweder nichts mehr für den Hash übrig bleibt (=> also eine konstante Funktion), oder man auch veränderliche Felder mit einbezieht.

Dein Argument, dass man auch bei immutable-Klassen die Objekte aus der Map entfernen muss, ist gut. Das bedeutet ja eigentlich, dass man egal wie hashCode() implementiert ist, auf jeden Fall Vorsicht walten lassen muss, wenn man den Objektzustand ändert.
Der einzige Unterschied ist dann wohl, dass man im einen Fall das Objekt vor der Änderung aus der Map entfernen muss und im anderen Fall das alte Objekt auch nachdem man ein neues erzeugt hat aus der Map löschen kann (sofern man noch eine Referenz darauf hat).

Ok, wenn das Objekt unveränderlich ist, dann kann man den Hash auch noch cachen.
 
Joshua Bloch hingegen schreibt in "Effective Java", dass alle Felder, die bei dem Vergleich in equals() relevant sind mit einbezogen werden müssen.
Das sagt er ja nicht, sondern das:
in hashCode() dürfen keine Felder geprüft werden, die nicht auch bei equals() geprüft werden.
Und das ist ja auch richtig so.

Eigentlich will man veränderbare Objekte gar nicht erst in hashbasierte Collections packen. Es wäre also auf jeden Fall eine Überlegung wert, alle Felder unveränderlich zu machen und ggf. bei Veränderungen ein neues Objekt zu erstellen.
 
Michael Inden behauptet in "Der Weg zum Java-Profi", dass eine hashCode() Methode nur die unveränderlichen Felder eines Objektes in die Berechnung mit einbeziehen darf.

Generell halte ich Inden für nicht empfehlenswert - der verbreitet viel unreflektiertes Halbwissen, und das halte ich für gefährlich.

Der Vertrag zwischen hashCode() und equals() ist klar in der API-Dok beschrieben:
Object (Java Platform SE 7 ))

Wie es weiter oben steht: mit equals bestimmst DU, wann ein Objekt gleich einem anderen ist. Und hashCode() ziehst Du dem Vertrag entsprechend nach.
 
Der Vertrag zwischen hashCode() und equals() ist klar in der API-Dok beschrieben:
Object (Java Platform SE 7 ))

Danke für den Hinweis. Da hätte ich auch selbst drauf kommen können... Da es ja so klar vorgegeben ist, führt es jedenfalls zu keinem unerwarteten Verhalten, wenn man es so implementiert.

Generell halte ich Inden für nicht empfehlenswert - der verbreitet viel unreflektiertes Halbwissen, und das halte ich für gefährlich.
Das ist mir leider auch schon mehrfach aufgefallen, während ich das Buch lese. Es werden Punkte am Java API kritisiert und einfach so als "Designfehler" dargestellt, ohne eine Diskussion zu führen. Dabei werden vor allem Punkte ausgelassen, weshalb diese Designentscheidung vielleicht doch gar nicht so schlecht war oder zumindest welche Vorteile sie bietet.

Ich habe mich in diesem Fall von den positiven Meinungsäußerungen hier im Forum und auch bei den Amazon Rezensionen hinreißen lassen und hielt es für eine gute Entscheidung für ein weiterführendes Buch zu Java um den Programmierstil zu verbessern.
 
Gute Bücher über Java gibt es zB. von Josh Bloch, "Effective Java 2nd Edition".
Generell würde ich empfehlen, sich an englische Originalausgaben von Büchern zu halten (selbst bei Bloch findet man mal eine Fehler, auch ohne dass der Übersetzer eigene Fehler/Mehrdeutigkeiten einbringt).
Ansosnten kann man auch immer was von Martin Fowler, Bob Martin oder Kent Beck lesen.

Darf man fragen wer sich hier im Forum positiv über das Inden-Buch geäussert hat?
So aus Neugier..
 
Gute Bücher über Java gibt es zB. von Josh Bloch, "Effective Java 2nd Edition".
Generell würde ich empfehlen, sich an englische Originalausgaben von Büchern zu halten (selbst bei Bloch findet man mal eine Fehler, auch ohne dass der Übersetzer eigene Fehler/Mehrdeutigkeiten einbringt).
Seitdem ich mit einem C++ Buch mal total reingefallen bin, kaufe ich ausschließlich englische Originalausgaben.

Ansosnten kann man auch immer was von Martin Fowler, Bob Martin oder Kent Beck lesen.
Von Martin Fowler habe ich Refactoring und Patterns of Enterprise Application Architecture gelesen. Design Patterns - Elements of Reusable Software ist eines der weiteren hochkarätigen Bücher.

Was mir noch fehlt, ist ein Buch, mit dem ich Java als Sprache besser beherrschen lerne. Durch andere Bücher und Erfahrung merke ich häufig selbst, wenn in einem Buch fragwürdige Äußerungen getroffen werden.
Was ich auf keinen Fall wollte, ist ein Buch, in dem die Grundlagen wieder von Adam und Eva an erklärt werden. Das hängt mir nach dem X-ten Grundlagenbuch zu irgendeiner Programmiersprache zum Halse raus. Nichtsdestotroz quäle ich mich da jedes mal aufs neue durch, denn man lernt ja nie aus...

Darf man fragen wer sich hier im Forum positiv über das Inden-Buch geäussert hat?
So aus Neugier..

Hier ging es los und einige Nutzer hatten sich positiv geäußert.
http://www.java-forum.org/buecher-tutorials-links/111159-liste-empfohlenen-buechern-2.html#post889481
 
Was ich wärmstens empfehlen kann ist "Clean Code" von Bob Martin und eben Effective Java von Bloch, Bloch hat auch einige Videos auf Google Tech Talk, sind auch sehr wehenswert imho.

Hier ging es los und einige Nutzer hatten sich positiv geäußert.
Liste von empfohlenen Büchern
Danke, hab dort mal eine etwas weniger optmistische Einschätzung dieses Buches hinterlassen, sonst werden noch mehr Leute in die falsche Richtung geleitet.
 
Was ich wärmstens empfehlen kann ist "Clean Code" von Bob Martin und eben Effective Java von Bloch

Danke für die Empfehlungen, dort werde ich mich dann als nächstes austoben. Ich war da ich Umsteiger bin auf der Suche nach einem qualitativ hochwertigen Buch das nicht ganz von vorne anfängt und umfassend auf die Sprache Java eingeht (also auf das JDK und Spracheigenheiten).
Allgemeine Bücher habe ich wie aus meinem letzten Posting ersichtlich auch vorher schon gelesen (neben einem Haufen anderer Bücher zu anderen Programmiersprachen).
 
Bloch bespricht zwar auch Grundlagen (zumindest anfangs), zeigt dann aber auch an "Grenzwertige" Fälle die selbst gestandene Entwickler immer wieder schockieren bzw. zum Grübeln bringen.

Bloch hat an Java 5 viel mitentwickelt (u.a. das Collection Framework, Generics, etc. pp.) und hat daher auch viel Ahnung vom Stoff.

In "Clean Code" von Bob Martin sind die Beispiele zwar in Java, ist aber recht universell einsetzbar und nicht nur auf Java beschränkt.
 
Dann ist Effective Java definitiv das richtige für mich.

Clean Code geht dann vom Stil wohl eher in die Richtung wie die o. g. Bücher von Fowler und der GOF und ist erst danach dran.

Ich kann mich nur noch einmal bedanken, eine konkrete Empfehlung ausgesprochen bekommen zu haben.
 
Achte darauf dass es die 2nd Edition von Effective Java ist 😉

Buch ist in der zweiten Auflage bestellt ;-) Das Buch von Inden werde ich trotzdem zuende lesen, wahrscheinlich aber viel auslassen, weil diese Themen anderswo besser behandelt werden (Refactoring, Effective Java...). Bei den aktuellen Themen steht vielleicht noch das ein oder andere interessante drin.

Dann hoffe ich mal, dass das neue Buch schnell ankommt!
 
Ehrlich gesagt, liest sich Inden da oben reichlich lächerlich. Wenn ich eine Immutable-Klasse habe, dann geb' ich "hashCode()" als zuvor im Konstruktor berechnete Konstante zurück. Meistens aber sehen die "hashCode()"-Implementationen sogar schon in der Standard-API anders aus - z.B. jene von AbstractList:
Java:
    /**
     * Returns the hash code value for this list.
     *
     * <p>This implementation uses exactly the code that is used to define the
     * list hash function in the documentation for the {@link List#hashCode}
     * method.
     *
     * @return the hash code value for this list
     */
    public int hashCode() {
	int hashCode = 1;
	Iterator<E> i = iterator();
	while (i.hasNext()) {
	    E obj = i.next();
	    hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
	}
	return hashCode;
    }
Eindeutig 'ne Mutable-Klasse. Und Inden will uns sagen, dass man Array-, Linked- oder sonstige Lists besser nicht in HashSets verwenden sollte? :lol:
Wenn ich dort nur Immutables verwenden sollte, bräuchte man doch nicht eine solch' dynamische Art des hashCode-Mechanismusses.
 
Eindeutig 'ne Mutable-Klasse. Und Inden will uns sagen, dass man Array-, Linked- oder sonstige Lists besser nicht in HashSets verwenden sollte? :lol:
Wenn ich dort nur Immutables verwenden sollte, bräuchte man doch nicht eine solch' dynamische Art des hashCode-Mechanismusses.
Zumindest als Key bei Maps sollte man auch auf evtl. Seiteneffekte vorbereitet sein.
Selbst die Java API weißt einen auf die Problematik mit mutable Objects als Key hin:
Map (Java Platform SE 7 )
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the equals and hashCode methods are no longer well defined on such a map.

Kommt also dann auch auf die Implementierung der Map an.

In Effective Java steht ebenfalls, dass immutable Objects sehr gute Keys für Maps sind.
[...] immutable objects make great map keys and set elements: you don't have to worry about their values changig once they're in the map or set, which would destroy the map or set's invariants.
Bin mir gerade nicht sicher, ob es da nicht sogar ein Java Puzzle gab, dass einen Seiteneffekt demonstrierte.

Schließt beides aber nicht vollkommen aus, nicht auch mutable Objekte zu verwenden. Ist eben eine Empfehlung.
Ist wohl auch immer die Frage, was möchte ich tun und was ist dann für diesen Anwendungsfall die beste Lösung.
 
Zuletzt bearbeitet:
Das mag ja alles stimmen, aber was dabei immer aufstösst ist, dass solche, solche und andere Klassen hier, da und dort nichts zu suchen haben. Vllt. sagt einem aber auch nie jemand, wie man z.B. Sets, SortedSets, Maps und SortedMaps korrekt verwendet, nämlich genau erst dann, wenn benötigt, speziell SortedSets bzw. -Maps evtl. sogar explizit mit speziellem Comparator. Ansonsten genügt es nämlich völlig, wenn man Iterables, Collections und Lists herumreicht. Ich weis gar nicht, wie hoch der Anteil an Mutables sein mag, wenn man alle Klassen der Welt stapeln würde, aber ich wette, der Anteil an Immutables wäre dagegen verschwindend gering. Und dann kommt so'n Buchautor daher und sagt über 90% aller Klassen seien für Maps bzw. Sets ungeeignet.
 
Entities sind zB. Mutable.
Wäre auch etwas umständlich bie jeder Änderung die alte Entity zu löschen inkl. aller refernzen, dann eine neue anzulegen und alle vorher entfernten referenzen neu anzulegen...

Immutable ist eben nicht immer eine mögliche Option, leider wird das nicht immer verstanden...
 
Immutable ist eben nicht immer eine mögliche Option, leider wird das nicht immer verstanden...
:lol: Wie wahr... Aber ernsthaft, ich denke mal, dass 90% aller existierenden Klassen Mutable sind und auch nichts anderes sein können. Wer dann nicht versteht, dass Immutable eher selten statt "nicht immer" eine Option ist, der sollte doch besser bei Basic oder ähnlichem bleiben.
 
Immutable kann ja auch gar nicht immer eine Lösung sein. Patterns wie das Observer Pattern hätten dann ja überhaupt keine Daseinsberechtigung mehr. Denn wenn sich ein Objekt nicht ändern kann, dann müsste es ja auch niemanden mehr benachrichtigen...
 
Eric Evan hat in seinem Buch "Domain Driven Design" schön erklärt, wann man was zu Immutables machen kann, zB. auch das zerlegen von Mutables in Mutable Objekte und Immutable Objekte.
Bloch geht darauf auch kurz ein, und ich glaube Robert Martin auch.

Grundsätzlich sind sog. Werte Objekte (ValueObjects), also Objekte deren Identität sich rein durch die Werte definiert, oft Kandidaten für Immutable ValueObjects.
Beispiele dafür wären eben String, durch die Java API vorgegeben, da es egal sein kann ob man einen String "Hans" durch einen anderen String mit dem wert "Hans" ersetzt.
Ein anderes Beispiel wäre zB. Money, ein 0,- € Schein ist so gut wie jeder andere 20,- € Schein, wenn man die Seriennummer ignoriert 😉
Bei Person zB. wäre das anders, da hat man irgendwo eine ID welche die Identität festlegt, 2 Personen mit allen Attributen gleich (Name, Geburtsdatum, Geschlecht etc. pp.) sind nunmal trotzdem unterschiedlich.
Person wäre eine Entity nach Evans definition.
 
Wird der Hashcode aus dem Inahlt eines Objekte berechnet, muss man bei der Verwaltung in einem HashContainer, z.B. HashSet, HashMap, usw. dizipliniert vorgehen. D.h. das Objekt wird vor der Änderung aus dem HashContainer entfernt und nach der Änderung wieder eingefügt. Ansonsten kann es passierten, dass das Objekt im HashContainer nicht mehr gefunden wird, bzw. der HashContainer zerstört wird. Einen Interessanten Artikel hierzu von Angelika Langer Fundamentals - Hash Code in Java - Consistency between equals() and hashCode()
 
Ich zitiere einmal einen kurzen Absatz (nicht allzu viel Kontext, aber meiner Meinung nach der Kern - ich hoffe, dass das noch keine Urheberrechtsverletzung ist...)

Der Weg zum Java Profi hat gesagt.:
Typische Fehler bei der Implementierung der Methode
Code:
hashCode()

Gerade für die Methode
Code:
hashCode()
sieht man häufiger Realisierungen, die gegen die Forderungen des Kontrakts verstoßen.
Ein typischer Fehler ist, dass die Methode
Code:
equals(Object)
überschrieben wird, die Methode
Code:
hashCode()
jedoch nicht. Dadurch wird in der Regel die Zusicherung verletzt, die besagt, dass für zwei laut
Code:
equals(Object)
gleiche Objekte auch der gleiche Wert durch
Code:
hashCode()
berechnet wird.
Manchmal sieht man den Fehler, dass auch nicht konstante Attribute eines Objekts zur Berechnung in
Code:
hashCode()
verwendet werden. Selbst einige professionelle Sourcecode-Generatoren und (sogar die in Eclipse eingebaute Automatik) generieren derartige hashCode()-Methoden, deren Wert sich aus verschiedenen, auch veränderlichen Attributen berechnet. Dies widerspricht der Forderung des
Code:
hashCode()
-Kontrakts nach Eindeutigkeit und Konstanz des Werts zur Laufzeit einer JVM: der berechnete Hashwert ändert sich dann bei jeder Modifikation eines zur Berechnung verwendeten Attributs. Dies führt allerdings dazu, dass wir Objekte in Hashcontainern nicht mehr wiederfinden, da bedingt durch den veränderten Wert, im falschen Bucket gesucht wird.

Die Formatierung stellt keine Wertung von mir dar, sondern ist im Buch so wiederzufinden. Der erste Teil ist einleuchtend, der zweite diskutabel.
 
Und ich zitiere mal die Doku von hashCode:
The general contract of hashCode is:

- Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, ...

Bis dahin deckt sich das mit der im zitierten Text suggerierten Eindeutigkeit. Die Doku geht aber noch weiter, und das ist in diesem Zusammenhang der entscheidende:

The general contract of hashCode is:

- Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.

Die Möglichkeit, dass der hashCode sich ändert (wenn sich das ändert, was zu equals beiträgt) ist damit explizit abgedeckt. Indirekt steht das auch nochmal bei der Doku von 'Set':
Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.

Als für mich ist klar: Der hashCode darf sich ändern. Aber man muss aufpassen.
 
Sicher wirft der durch die Spezifikation vorgegebene Rahmen unmittelbare Fragen auf. Ganz pragmatisch: Darf ICH an MEINEM Objekt irgendwas ändern, was den HashCode ändert? Und die Antwort ist ... etwas unbefriedigend, weil in der Praxis nur bedingt überprüfbar: Ja, das darfst du, WENN du sicherstellst, dass das Objekt in keiner Datenstruktur (Set) liegt, die sich auf equals/hashCode verläßt. Das kann man nur sicherstellen, wenn man die Objekte nie "nach draußen" gegeben hat, und selbst intern ist es manchmal schwierig...
 

Zurück
Oben