1 // EqualsTest.java
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