Ich kann mich noch vage daran erinnern, als ich mich 1997 das erste Mal mit Java beschäftigte. Ich probierte mich an meinen ersten .java Dateien und hangelte mich an Java Tutorial entlang. Ziemlich bald stieß ich auf auf die equals Methode. Ich war begeistert. Jede Klasse kann selbst entscheiden, welche Instanzen von ihr als gleich gelten. Cool - dachte ich damals.
Wie bei so vielem hat sich die Begeisterung nach einiger Zeit gelegt. Wie jeder Java Entwickler mittlerweile begriffen haben sollte, ist equals nicht korrekt implementierbar, wenn man per Vererbung Eigenschaften zu Klassen hinzufügt. Wer es noch nicht kennt, kann es in Effective Java online nachlesen. Wer das Buch noch nicht hat, sollte es vermutlich schleunigst bestellen.
Aber das ist nur die technische Seite. Die bekommt man durch Vermeidung von Hierarchien mit konkreten Klassen ganz gut in den Griff. Das Problem das ich heute ins Bewusstsein bringen will, ist dass das Konzept prinzipiell Fehlerhaft ist. Meine Behauptung ist:
Es gibt kein korrektes EQUAL!
Warum? Man frage einen Mathematiker: Was ist Gleichheit? Er wird sagen (so in etwa) eine Relation mit bestimmten Eigenschaften. Und das wichtige dabei: EINE Relation von beliebig vielen. Java bietet primär aber nur zwei an: equals und Identität (==), und das reicht einfach nicht.
Beispiel: Eine Datenbank basierte Anwendung.
Eine wichtige Gleichheits Relation ist sicherlich die tatsächliche Zeile in der Datenbanktabelle aus der eine Instanz entstanden ist.
Eine andere ist der sogenannte fachliche Schlüssel, also z.B. die E-Mailadresse einer Person.
Eine weitere der korrekte fachliche Schlüssel, also z.B. die E-Mailadresse modulo aller Tippfehler.
Die nächste die Gleichheit bezüglich aller enthaltenen Felder, wobei dann natürlich sofort die Frage entsteht, welche Gleichheitsrelation bei referenzierten Objekten verwendet werden sollen.
Eine wichtige, oft vergessene Gleichheitsrelation ist die Gleichheit in der Darstellung. Zwei Objekte, die gleich dargestellt werden, können vom Benutzer nicht unterschieden werden, sollten also auch nie innerhalb einer Darstellung als unterschiedliche Objekte verwendet werden.
Alles nur ein theoretisches Problem? Mit nichten. Längliche Diskussionen über die korrekte Implementation von Identität im Hibernate Umfeld belegen die real existierenden Schwierigkeiten.
Hibernate hat eine Bestimmte Art und Weise mit Identität umzugehen. Danach kann man sich ausrichten. Leider haben auch andere Bibliotheken dies, z.B. JGoodies. Prinzipiell hat jede Bibliothek, die eine Map oder ein Set verwendet eine Interpretation von Gleichheit, die nicht immer mit der anderer Bibliotheken identisch sein muss.
Und die Lösung?
Die Lösung lautet wie so oft: Nachdenken! Welche Bereiche meiner Anwendung benötigen welche Interpretation von Gleichheit. Kann ich mich auf die einfache und wohldefinierte Identität zurückziehen? Kann ich mich darauf verlassen, dass die equals()-Implementation der verwendeten Klassen korrekt ist? Und wenn nicht, wie kann ich an den entscheidenden Stellen die richtige Implementierung zur Verfügung stellen? Bei Drittbibliotheken könnte dies durch Proxy Objekte möglich sein, manchmal auch durch Comparator Implementierungen. Wenn man selbst eine bestimmte Interpretation von Gleichheit erfordert, die implementierenden Klassen aber möglichst wenig einschränken möchte, könnte ein Lookup Mechanismus helfen: Anstatt direkt equals() aufzurufen, wird entweder in einer zentralen Map, oder per Namenskonvention oder ähnlichem nach einem geeigneten Comparator gesucht, und nur wenn dieser nicht gefunden wird, dann wird equals verwendet.
Diese prinzipielle Vorgehen würde ich mir auch bei anderen Eigenschaften, die Bibliotheken von Klassen einfordern wünschen: Weniger auf Implementierung bestimmter Methoden setzen, dafür mehr auf per Konvention oder im Notfall Konfiguration auffindbare Implementierungen setzen.
Talks
Wan't to meet me in person to tell me how stupid I am? You can find me at the following events: