CodeGym /Kurse /JAVA 25 SELF /Interface Comparable: Implementierung, compareTo

Interface Comparable: Implementierung, compareTo

JAVA 25 SELF
Level 29, Lektion 2
Verfügbar

1. Das Problem des Vergleichens von Objekten

Zur Erinnerung: Referenzvergleich vs. Objektvergleich

Sie wissen bereits, dass der Operator == bei Objekten ihre Referenzen vergleicht – also ob sie an derselben Adresse im Speicher liegen. Zwei Objekte mit identischen Feldern, aber jeweils mit new erstellt, sind für == verschieden.

Person p1 = new Person("Alex", 20);
Person p2 = new Person("Alex", 20);

System.out.println(p1 == p2); // false – das sind verschiedene Objekte im Speicher!

Wenn wir jedoch wissen wollen, ob sie inhaltlich gleich sind, verwenden wir equals(), hashCode() … das kennen Sie schon. Und was, wenn wir verstehen möchten, wer „älter“, „jünger“, „weiter vorne im Alphabet“ ist? Zum Beispiel, um eine Liste von Benutzern nach Alter oder Name zu sortieren.

Die Notwendigkeit von Sortierung und Suche von Objekten

Nehmen wir an, wir haben eine Liste von Benutzern und möchten sie nach Alter sortieren:

List<Person> people = new ArrayList<>();
people.add(new Person("John", 25));
people.add(new Person("Peter", 20));
people.add(new Person("Kate", 30));

// Wie sortieren?
Collections.sort(people); // Ups! Java weiß nicht, wie man Person vergleicht!

Der Compiler beschwert sich sofort: Die Klasse Person implementiert das Interface Comparable nicht. Java kann keine Gedanken lesen und weiß nicht, was „größer“ oder „kleiner“ für Person bedeutet. Damit Java das kann, müssen wir die Vergleichsregeln explizit beschreiben.

2. Das Interface Comparable

Deklaration des Interfaces

Das Interface Comparable ist der Standardweg, Java mitzuteilen: „Meine Klasse ist vergleichbar – und so wird verglichen.“

public interface Comparable<T> {
    int compareTo(T o);
}

Der altbekannte Aufruf a.compareTo(b) liefert:

  • eine negative Zahl – dann ist a „kleiner als“ b.
  • Ist es 0 – gelten die Objekte als gleich.
  • eine positive Zahl – a ist „größer als“ b.

Beispiel: Implementierung von compareTo für die Klasse Person

Erstellen wir eine Klasse Person, die nach Alter vergleichbar ist:

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter (für die folgenden Beispiele)
    public String getName() { return name; }
    public int getAge() { return age; }

    // Implementierung der Methode compareTo
    @Override
    public int compareTo(Person other) {
        // Sortierung nach Alter (aufsteigend)
        return Integer.compare(this.age, other.age);
        // Alternative: return this.age - other.age;
    }
}

Wichtiger Punkt: Wenn Sie absteigend sortieren möchten, tauschen Sie einfach die Argumente: Integer.compare(other.age, this.age).

Analogie. compareTo ist wie ein Schiedsrichter im Wettkampf: Er muss klar entscheiden, wer vorne, wer hinten und wer auf gleicher Höhe liegt. Wenn alle Schiedsrichter (die Methoden compareTo) unterschiedlich pfeifen – entsteht Chaos!

3. Verwendung von Comparable

Collections mit Comparable sortieren

Jetzt, da unsere Klasse Comparable implementiert, funktioniert die Sortierung von Haus aus:

List<Person> people = new ArrayList<>();
people.add(new Person("John", 25));
people.add(new Person("Peter", 20));
people.add(new Person("Kate", 30));

Collections.sort(people); // Verwendet compareTo!

for (Person p : people) {
    System.out.println(p.getName() + " (" + p.getAge() + ")");
}
// Peter (20)
// John (25)
// Kate (30)

Ähnlich funktioniert auch die Methode sort bei Listen:

people.sort(null); // Wenn man null übergibt, wird compareTo verwendet

Sortierung nach dem Namen

Wenn wir nach dem Namen sortieren möchten – ändern wir die Implementierung:

@Override
public int compareTo(Person other) {
    return this.name.compareTo(other.name);
}

Sortierung nach mehreren Feldern

Manchmal muss man zunächst nach einem Feld vergleichen und bei Gleichstand nach einem anderen:

@Override
public int compareTo(Person other) {
    int cmp = Integer.compare(this.age, other.age);
    if (cmp != 0) return cmp;
    return this.name.compareTo(other.name);
}

4. Best Practices bei der Implementierung von Comparable

Den Comparable-Vertrag einhalten

  • Wenn a.compareTo(b) == 0 ist, dann muss b.compareTo(a) unbedingt ebenfalls 0 sein.
  • Wenn a.compareTo(b) < 0 ist, dann muss b.compareTo(a) > 0 liefern (und umgekehrt).
  • Wenn a.compareTo(b) == 0 ist, ist es wünschenswert, dass a.equals(b) true ist (aber nicht zwingend erforderlich).

Warum ist das wichtig?
Collections (z. B. TreeSet, TreeMap) und Sortiermethoden können sich unvorhersehbar verhalten, wenn der Vertrag verletzt wird. Beispielsweise können „Duplikate“ in einer Collection auftauchen, in der sie nicht sein sollten.

Vergessen Sie equals und hashCode nicht

Wenn Sie compareTo implementieren, denken Sie daran: Sind equals und hashCode korrekt implementiert? Besonders wichtig, wenn Ihre Klasse in Collections wie HashSet oder Map verwendet wird.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Person)) return false;
    Person other = (Person) o;
    return age == other.age && Objects.equals(name, other.name);
}

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

Verwenden Sie in compareTo keine Felder, die null sein können, ohne Prüfung!

Wenn ein Feld null sein kann, verwenden Sie sichere Vergleiche:

@Override
public int compareTo(Person other) {
    return Objects.compare(this.name, other.name, Comparator.nullsFirst(String::compareTo));
}

Ändern Sie keine Felder, die in compareTo verwendet werden, wenn sich das Objekt bereits in einer sortierten Collection befindet

Das kann dazu führen, dass das Objekt in der Collection „verlorengeht“ – z. B. in TreeSet oder TreeMap.

5. Wir entwickeln die Übungsanwendung weiter: Sortierung von Benutzern

Schritt 1: Klasse definieren

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    // ... Konstruktor, Getter, compareTo, equals, hashCode ...
}

Schritt 2: Benutzer hinzufügen

List<Person> people = new ArrayList<>();
people.add(new Person("Mary", 23));
people.add(new Person("Greg", 19));
people.add(new Person("Ann", 25));

Schritt 3: Sortieren und ausgeben

Collections.sort(people);

for (Person p : people) {
    System.out.println(p.getName() + " (" + p.getAge() + ")");
}

Ergebnis:

Greg (19)
Mary (23)
Ann (25)

6. Ablauf von Comparable

┌────────────────────────────┐
│   Ihre Klasse (Person)     │
├────────────────────────────┤
│ implements Comparable      │
│   ↓                        │
│ public int compareTo(T o)  │
│   ↓                        │
│ (this < o) → -1            │
│ (this == o) → 0            │
│ (this > o) → 1             │
└────────────────────────────┘
        │
        ▼
Collections.sort(list)
        │
        ▼
   Sortierung funktioniert!

7. Nützliche Details

Wie Collections.sort funktioniert

  • Wenn die Liste Objekte enthält, die Comparable implementieren, verwendet die Sortierung deren Methode compareTo.
  • Wenn nicht implementiert – gibt es einen Compilerfehler.
  • Für Standardtypen (Integer, String u. a.) ist Comparable bereits implementiert.

Kann man mehrere Vergleichsarten anbieten?

  • In einer Klasse – nur eine „natürliche Ordnung“ über Comparable.
  • Für alternative Ordnungen verwenden Sie Comparator (nächste Vorlesung).

Beispiel: compareTo für Strings

String a = "apple";
String b = "banana";
System.out.println(a.compareTo(b)); // negative Zahl, weil "apple" < "banana"

Tabelle: Was compareTo zurückgibt

Vergleich Rückgabewert
this < o
< 0
this == o
0
this > o
> 0

8. Typische Fehler bei der Implementierung von Comparable

Fehler Nr. 1: Verletzung des compareTo-Vertrags.
Wenn a.compareTo(b) 0 zurückgibt, aber b.compareTo(a) nicht 0 ist, verhalten sich Collections merkwürdig. Zum Beispiel kann TreeSet Objekte als verschieden ansehen und beide hinzufügen.

Fehler Nr. 2: Verwendung nicht initialisierter (null) Felder.
Wenn das Feld, nach dem Sie vergleichen, null sein kann und Sie nicht prüfen – erhalten Sie eine NullPointerException.

Fehler Nr. 3: Inkonsistenz zwischen compareTo und equals.
Wenn compareTo sagt, dass Objekte gleich sind (0), equals aber „verschieden“ (false), führt das zu Bugs im Umgang mit Collections.

Fehler Nr. 4: Ändern von Feldern, die in compareTo verwendet werden, nachdem das Objekt in eine sortierte Collection eingefügt wurde.
Das ist, als würden Sie Ihren Nachnamen im Pass ändern, während Sie bereits in einer alphabetischen Warteschlange stehen. Die Collection kann Ihr Objekt „verlieren“.

Fehler Nr. 5: Nur -1, 0 oder 1 zurückgeben.
Die Methode compareTo darf jede negative oder positive Zahl zurückgeben, nicht zwingend strikt -1 oder 1. Für die Einfachheit werden jedoch oft -1/0/1 verwendet.

1
Aufgabe
JAVA 25 SELF, Level 29, Lektion 2
Gesperrt
Sortierung des Buchkatalogs nach mehreren Kriterien 📖
Sortierung des Buchkatalogs nach mehreren Kriterien 📖
1
Aufgabe
JAVA 25 SELF, Level 29, Lektion 2
Gesperrt
Verwaltung der Einzigartigkeit und Rangfolge von Städten 🏙️
Verwaltung der Einzigartigkeit und Rangfolge von Städten 🏙️
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION