Demo zu equals(…), hashCode(), compareTo(…), Collections und Weiterem
Es wird eine einfache Personenklasse zun Demonstrieren der im Titel erwähnten Themen definiert. Sie hat Attribute, die viele Konzepte für Methode compareTo(..)
zeigen lassen.
Wichtige Aspekte:
-
Es werden zu Demonstrationszwecken zwei Konstante deklariert (
MIN_BIRTH_YEAR
,MAX_BIRTH_YEAR
). -
Es wird eine Enumeration (
enum
) verwendet:Gender
. -
Es wird das Interface
Comparable<T>
mit seiner einzigen Methodeint compareTo(T other)
"eingebaut". Damit lässt sich eine "natürliche" Ordnung festlegen, nach der standardmäßig sortiert werden soll
(Wir wählen als stärkstes Kriteriumsmoker
, dannlastname
,firstname
,birthYear
, zuletztgender
). -
In der
main(..)
-Methode wird eine Liste als Attribut mittels InterfaceList<T>
deklariert und mit KlasseArrayList<T>
instanziert. Darin wird die statische MethodeprintPersons(..)
aufgerufen und die Liste als Parameter übergeben. -
Methode
private static printPersons(Collection<Person> persons, String title)
kann die übergebene Liste als Parameter verwenden, da InterfaceList<T>
eine Spezialisierung von InterfaceCollection<T>
ist (analog auch InterfaceSet<T>
). -
Die Liste wird sortiert mittels
persons.sort(null);
Die in InterfaceList
definierte Methodesort(Comparator<? super E> c)
kann als Parameternull
erhalten, wenn die Elementklasse das InterfaceComparable<T>
implmentiert. Dann wird das mittels MethodecompareTo(..)
festgelegte natural ordering umgesetzt. -
Oben erwähnte Methode
printPersons(..)
verwendet eine foreach-Schleife. Markant an dieser ist, dass sie sehr kompakte Iteration ermöglicht, aber keine Laufvariable für den Index bereitstellt. Das macht sie aber auch zur Iteration über Collections ohne definierte Ordnung (Sets) fähig. -
In Methode
equals(..)
wird Methodejava.util.Objects.equals(obj1, obj2)
(mit Signaturpublic static boolean equals(Object a, Object b)
) verwendet. Diese funktioniert auch, wenn z.B.this.firstname
Wertnull
hat.
1. Basis-Info zur Demo-Klasse Person
public class Person implements Comparable<Person> {
public static final int MIN_BIRTH_YEAR = 1900; // durch 'final' konstant -->
public static final int MAX_BIRTH_YEAR = 2022; //.. MÜSSEN in Deklar. Wert erhalten
public enum Gender {
FEMALE, MALE, OTHER, UNKNOWN;
}
private String lastname;
private String firstname;
private Gender gender;
private int birthYear;
private boolean smoker;
private String email;
// ...
// @Override public int hashCode() { ... }
// @Override public boolean equals(Object obj) { ... }
// @Override public int compareTo(Person o) { ... } // definiert im Interface Comparable<Person>
}
2. Implementation hashCode() und equals(…)
Ein erster Anwendungsfall ist die Annahme, dass die 4 Attribute lastname
, firstname
, gender
und birthYear
Eindeutigkeit einer Person sicherstellen (stimmt nicht - dazu müsste das vollständige Geburtsdatum und der Geburtsort gegeben sein, dann wird im jeweiligen Standesamt dafür gesorgt, dass es keine 2 Franz Meier am selben Tag im selben Ort/Gemeindebezirk gibt).
Dies wird für die Re-Definition (Override) der beiden wichtigen, schon in der Root-Klasse Object definierten Methoden equals(…)
und hashCode()
benötigt.
Unter dieser Annahme und durch Auswahl dieser Attribute im Generator-Dialog kann man in der IDE die folgenden Methoden generieren.
Hier wurden in Eclipse die Checkboxen "Use 'instanceof' to compare types" und "Use Objects.hash and Objects.equals methods (1.7 or higher)" angekreuzt – dies macht die Methoden wesentlich kompakter.
Zusätzlich wurde in equals(…)
händisch das this.
vor die eigenen Instanzvariablen gesetzt, da damit die Symmetrie this
←→ other
besser sichtbar wird, weiters wurden erläuternde Kommentare eingefügt:
@Override
public int hashCode() {
// in der JDK-Methode Objects.hash(...) wird schon die ganze Arbeit erledigt:
return Objects.hash(this.birthYear, this.firstname, this.gender, this.lastname);
}
@Override
public boolean equals(Object obj) {
if (this == obj) // identisches Objekt übergeben (sich selbst)
return true;
if (!(obj instanceof Person)) // <-- falls 'obj' keine Person, kann es nicht gleich sein
return false; // |
Person other = (Person) obj; // obiges 'instanceof' sichert die Gültigkeit des type cast
return this.birthYear == other.birthYear && Objects.equals(this.firstname, other.firstname)
&& this.gender == other.gender && Objects.equals(this.lastname, other.lastname);
}
Bei Annahme, dass die E-Mail-Adresse Eindeutigkeit sichert (typisch für Online-Accounts) ist die Instanzvariable email
als Einziges auszuwählen und es entsteht bei gleicher Vorgehensweise:
@Override
public int hashCode() {
return Objects.hash(this.email);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Person))
return false;
Person other = (Person) obj;
return Objects.equals(this.email, other.email);
}
3. Implementation compareTo(…)
Die wegen Implementierens des Interface Comparable<T>
zu implementierende Methode public int compareTo(Person o) { … }
erfordert implizit die Festlegung, welche "natürliche" Ordnung man für Personen verwenden möchte.
Hier wählen wir als stärkstes Kriterium lastname
, dann firstname
, dann birthYear
, zuletzt gender
:
@Override
public int compareTo(Person other) {
int cmp;
if ((cmp = this.lastname.compareTo(other.lastname)) != 0) { // bei Gleichheit gilt cmp == 0
return cmp; // das erste Kriterium ergibt Ungleichheit und hat damit Klarheit geschaffen
}
if ((cmp = this.firstname.compareTo(other.firstname)) != 0) {
return cmp;
}
if ((cmp = this.birthYear - other.birthYear) != 0) {
return cmp;
}
if ((cmp = this.gender.compareTo(other.gender)) != 0) {
return cmp;
}
return 0; // alle Kriterien sind gleich, daher ist Gleichheit gegeben --> 0
// return this.email.compareTo(other.email); // falls nur email verwendet würde ...
}
Nun wollen wir einige Collection-Typen ansehen