Was passiert, wenn man die Wertklasse Datum
aus Teil 4
mittels Unterklasse DatumMitZeit
um die Uhrzeit erweitert?
Main-Klasse
equals.EqualsTest
Anders als Entity-Objekte, die immer nur mit sich selbst gleich sind,
sind Wert-Objekte gleich, wenn ihre Instanzvariablen die gleichen Werte haben.
Um das sicherzustellen, müssen Wertklassen die Instanzmethoden
equals(Object)
und hashCode()
aus java.lang.Object
überschreiben. Dabei muss equals
laut
API-Spezifikation
eine Äquivalenrelation sein, d.h. equals
muss
reflexiv, symmetrisch und transitiv sein.
Außerdem muss hashCode
laut
API-Spezifikation
für zwei Objekte denselben Hash-Code liefern,
wenn sie laut equals
gleich sind.
Die Main-Klasse EqualsTest
prüft die verlangten Eigenschaften von
equals
und hashCode
mit einigen Beispiel-Objekten der Oberklasse
Datum
und Unterklasse DatumMitZeit
.
Für die Klassen Datum
und DatumMitZeit
gibt es vier Implementierungsvarianten, jede in einem eigenen Unterpaket.
In EqualsTest
wird per import
-Anweisung festgelegt,
welche Implementierungsvariante getestet werden soll.
Paket equals.variante1
mit Oberklasse
Datum
und Unterklasse
DatumMitZeit
Die erste Implementierungsvariante funktioniert gut für Testfälle, die entweder
nur Datum
-Objekte oder nur DatumMitZeit
-Objekte enthalten.
Das Substitutionsprinzip der Klassenvererbung verlangt nun aber, dass Objekte einer
Oberklasse jederzeit durch Objekte ihrer Unterklassen ersetzt werden können,
d.h. es müssen auch gemischte Testfälle mit Datum
- und
DatumMitZeit
-Objekten richtig funktionieren.
Beim gemischten Testfall in EqualsTest
wird jedoch die Symmetrie
von equals
verletzt, weil bei a.equals(b)
und b.equals(a)
verschiedene Methoden aufgerufen werden, einmal die aus Datum
(sie liefert true
) und einmal die aus DatumMitZeit
(sie liefert false
).
Implementierungsvariante 1 ist also wegen Verletzung der Symmetrie falsch.
Paket equals.variante2
mit Oberklasse
Datum
und Unterklasse
DatumMitZeit
Die zweite Implementierungsvariante versucht die erste zu reparieren,
indem die equals
-Methode der Unterklasse die equals
-Methode
der Oberklasse aufruft. Die Symmetrie ist damit auch im gemischten Testfall gesichert,
dafür verletzt hashCode
die Spezifikation und die Transistivität
von equals
ist verletzt.
Implementierungsvariante 2 ist also auch falsch.
Paket equals.variante3
mit Oberklasse
Datum
und Unterklasse
DatumMitZeit
equals
-Methode der Oberklasse Objekte der Unterklasse prinzipiell
nicht mehr als gleich zu Objekten der Oberklasse betrachtet
(Verwendung von getClass()
statt instanceof
).
Implementierungsvariante 3 ist im Sinne der API-Spezifikation richtig,
aber weil sie das Substitutionsprinzip der Klassenvererbung verletzt,
ist sie schlecht.
Paket equals.variante4
mit Objektkomposition statt Vererbung mit
Datum
,
Zeit
und
DatumMitZeit
Die vierte Implementierungsvariante verhält sich exakt wie die dritte,
verletzt aber das Substitutionsprinzip nicht, weil sie ohne Klassenvererbung auskommt.
Diese Lösung ohne Klassenvererbung wird auch im Paket
java.time
der Klassenbibliothek verwendet (Klassen LocalDate
, LocalTime
und LocalDateTime
).
Fazit: Klassenvererbung und Wertklassen passen nicht zusammen.
Wertklassen müssen final
sein und dürfen abgesehen von
java.lang.Object
höchstens abstrakte Oberklassen haben.