2 package equals;
3 import equals.variante1.Datum;
4 import equals.variante1.DatumMitZeit;
5
6 /**
7 * EqualsTest testet equals-Implementierungen auf Reflexivität,
8 * Symmetrie und Transitivität.
9 * Beispielprogramm zur Programmiertechnik 1, Teil 5.
10 * Testet die Implementierungsvarianten 1, 2, 3 und 4 von equals
11 * in den Klassen Datum und DatumMitZeit.
12 * @author H.Drachenfels
13 * @version 15.6.2020
14 */
15 public final class EqualsTest {
16 private EqualsTest() { }
17
18 /**
19 * main ist der Startpunkt des Programms.
20 * @param args wird nicht verwendet.
21 */
22 public static void main(String[] args) {
23 Object[][] testDaten = {
24 {
25 Datum.valueOf(16, 6, 2020),
26 Datum.valueOf(16, 6, 2020),
27 Datum.valueOf(16, 6, 2020),
28 },
29 {
30 Datum.valueOf(16, 6, 2020),
31 Datum.valueOf(16, 6, 2020),
32 Datum.valueOf(17, 6, 2020)
33 },
34 {
35 DatumMitZeit.valueOf(16, 6, 2020, 9, 45),
36 DatumMitZeit.valueOf(16, 6, 2020, 9, 45),
37 DatumMitZeit.valueOf(16, 6, 2020, 9, 45),
38 },
39 {
40 DatumMitZeit.valueOf(16, 6, 2020, 9, 45),
41 DatumMitZeit.valueOf(16, 6, 2020, 9, 45),
42 DatumMitZeit.valueOf(16, 6, 2020, 10, 15)
43 },
44 {
45 DatumMitZeit.valueOf(16, 6, 2020, 9, 45),
46 Datum.valueOf(16, 6, 2020),
47 DatumMitZeit.valueOf(16, 6, 2020, 10, 15)
48 }
49 };
50
51 for (Object[] testFall : testDaten) {
52 System.out.printf("Testfall %s%n",
53 java.util.Arrays.toString(testFall));
54
55 // reflexiv?
56 for (Object o : testFall) {
57 if (!o.equals(o)) {
58 throw new AssertionError("equals nicht reflexiv: " + o);
59 }
60 }
61
62 System.out.println("equals in diesem Testafall reflexiv");
63
64 // symmetrisch?
65 for (int i = 0; i < testFall.length - 1; ++i) {
66 Object a = testFall[i];
67 Object b = testFall[i + 1];
68 if (a.equals(b) != b.equals(a)) {
69 // kann bei equals.variante1 passieren
70 throw new AssertionError("equals nicht symmetrisch: "
71 + a + " " + b);
72 }
73 }
74
75 System.out.println("equals in diesem Testfall symmetrisch");
76
77 // hashCode kompatible mit equals?
78 for (int i = 0; i < testFall.length - 1; ++i) {
79 Object a = testFall[i];
80 Object b = testFall[i + 1];
81 if (a.equals(b) && a.hashCode() != b.hashCode()) {
82 // kann bei equals.variante1 und equals.variante2 passieren
83 System.out.println(
84 "Fehler: equals true, aber Hashcodes verschieden: "
85 + a + " " + b);
86 }
87 }
88
89 // transitiv?
90 Object a = testFall[0];
91 Object b = testFall[1];
92 Object c = testFall[2];
93 if (a.equals(b) && b.equals(c)) {
94 if (!a.equals(c)) {
95 // kann bei equals.variante2 passieren
96 throw new AssertionError("equals nicht transistiv: "
97 + a + " " + b + " " + c);
98 }
99
100 System.out.println("equals in diesem Testfall transitiv");
101 } else {
102 System.out.println("transitiv nicht testbar");
103 }
104
105 System.out.println();
106 }
107
108 /*
109 * Nur die Varianten 3 und 4 setzen die Spezifikation von
110 * equals und hashCode richtig um.
111 *
112 * Variante 3 verletzt allerdings das Substitutionsprinzip der
113 * Objektorientierung. Ein Oberklasse-Objekt (hier Datum) soll
114 * danach ohne Funktionalitaetsverlust durch ein Unterklasse-Objekt
115 * (hier DatumMitZeit) ersetzt werden koennen. Bei Variante 3 geht
116 * dabei aber die Vergleichbarkeit mit equals verloren.
117 *
118 * Konsequenzen:
119 * equals und hashCode nur in final oder abstract markierten Klassen
120 * ueberschreiben, die keine (abgesehen von Object) oder nur abstrakte
121 * Oberklassen haben.
122 */
123 }
124 }
125