
Dieser Artikel erklärt:
- Wie vergleicht man zwei Objekte in Java?
- Was ist ein
Comparatorund wofür benötigt man ihn? - Welche Möglichkeiten gibt es, um einen
Comparatorzu erzeugen? - Wie erzeugt man einen
ComparatormitComparator.comparing()? - Was ist der Unterschied zwischen
ComparatorundComparable?
Doch vorab die Frage: Warum möchte man überhaupt Java-Objekte miteinander vergleichen?
Das wichtigste Einsatzgebiet für den Vergleich zweier Objekte ist das Sortieren von Objekt-Listen oder -Arrays. Um Objekte zu sortieren, muss das Programm sie vergleichen und herausfinden, ob ein Objekt kleiner, größer oder gleich einem anderen ist.
Die Interfaces Comparable und Comparator, um die es in diesem Artikel geht, steuern auch die Reihenfolge in TreeMap und TreeSet, den Kopf einer PriorityQueue, das Ergebnis von Collections.max() und die Sortierung in Stream.sorted().
Den Quellcode zum Artikel findest du im zugehörigen GitHub-Repository.
Wie vergleicht man zwei Objekte in Java?
Java-Primitive (int, long, double, etc.) vergleicht man mit den Operatoren <, <=, ==, !=, >=, >.
Bei Objekten funktioniert das nicht: < und > lässt der Compiler nicht zu, und == prüft nur, ob es dasselbe Objekt ist.
Dort verwendet man stattdessen entweder eine compare()- oder eine compareTo()-Methode. Beide Methoden erkläre ich am Beispiel von Strings und der selbst geschriebenen Klasse Student.
Wie vergleicht man zwei Strings in Java?
Nehmen wir an, wir haben die folgenden zwei Strings:
String s1 = "Happy";
String s2 = "Coders";
Wir wollen nun herausfinden, ob s1 kleiner, größer oder gleich s2 ist. Oder anders gesagt: ob – nach alphabetischer Sortierung – s1 vor oder nach s2 käme.
Das tun wir wie folgt:
int result = s1.compareTo(s2);
Die Variable result enthält nun:
- einen Wert kleiner als 0, wenn
s1nach alphabetischer Sortierung vors2kommt - 0, wenn
s1unds2gleich sind (d. h., dasss1.equals(s2)trueergibt) - einen Wert größer als 0, wenn
s1nach alphabetischer Sortierung nachs2kommt
Im Beispiel oben wäre result größer als 0, da „Happy“ hinter „Coders“ einsortiert werden würde.
Sortieren von Strings in Java
Die compareTo()-Methode wird intern von Arrays.sort() und Collections.sort() aufgerufen, um ein Array oder eine Liste von Objekten zu sortieren, beispielsweise wie folgt:
public class NameSortExample {
public static void main(String[] args) {
String[] names = {"Mary", "James", "Patricia", "John", "Jennifer", "Robert"};
Arrays.sort(names);
System.out.println(Arrays.toString(names));
}
}
Das Programm gibt die Namen in alphabetischer Reihenfolge aus:
[James, Jennifer, John, Mary, Patricia, Robert]
Alle weiteren Wege, Objekte und Primitive wie int, long und double zu sortieren, zeigt das Tutorial „Sortieren in Java“.
Was aber, wenn wir Strings gar nicht alphabetisch sortieren wollen, sondern beispielsweise nach ihrer Länge? Dazu brauchen wir einen sogenannten Comparator. Bevor wir dazu kommen, erkläre ich erst einmal das Interface Comparable, das wir soeben verwendet haben.
Das Java-Comparable-Interface
Die String.compareTo()-Methode, die wir oben angewendet haben, kommt aus dem java.lang.Comparable-Interface, welches von der String-Klasse implementiert wird.
Das Comparable-Interface definiert nur diese eine Methode. Es wird von allen Klassen implementiert, deren Objekte vergleichbar (englisch: „comparable“) sein sollen. Neben String sind das beispielsweise Integer, Long, BigInteger, LocalDate, LocalDateTime, Instant und viele mehr.
Die Reihenfolge, die sich aus der compareTo()-Methode ergibt, nennt man „natürliche Ordnung“: Strings werden alphabetisch sortiert, Datums- und Zeitwerte in ihrer zeitlichen Abfolge.
Für compareTo() gilt ein sogenannter Contract (eine Vereinbarung, an die sich jede Implementierung halten muss): Das Vorzeichen dreht sich um, wenn du die Seiten tauschst (sgn(a.compareTo(b)) == -sgn(b.compareTo(a))), und die Ordnung ist transitiv – ist a kleiner als b und b kleiner als c, dann ist a kleiner als c. Verletzt eine Implementierung den Contract, ist das Sortierergebnis undefiniert. Mitunter fällt es dem JDK selbst auf: Der Sortieralgorithmus TimSort wirft seit Java 7 eine IllegalArgumentException mit der Meldung „Comparison method violates its general contract!“.
Wie du eine selbst entwickelte Klasse vergleichbar (und damit sortierbar) machst, liest du im folgenden Abschnitt.
Java-Comparable-Beispiel
Um selbstentwickelte Klassen vergleichbar und damit sortierbar zu machen, müssen sie das Interface Comparable und dessen compareTo()-Methode implementieren.
Das folgende Beispiel zeigt, wie man dies für eine Student-Klasse macht, deren Objekte standardmäßig nach Matrikelnummer (ID) sortiert werden sollen:
public class Student implements Comparable<Student> {
private int id;
private String firstName;
private String lastName;
// ... constructor ...
// ... getters and setters ...
// ... toString() method ...
@Override
public int compareTo(Student o) {
if (this.id < o.id) {
return -1;
} else if (this.id == o.id) {
return 0;
} else {
return 1;
}
}
}
Mit dem ternären Operator kannst du die compareTo()-Methode auch als Einzeiler notieren:
@Override
public int compareTo(Student o) {
return this.id < o.id ? -1 : (this.id == o.id ? 0 : 1);
}
Noch eleganter ist es allerdings, für den Vergleich der zwei int-Werte die statische Methode Integer.compare() aufzurufen:
@Override
public int compareTo(Student o) {
return Integer.compare(this.id, o.id);
}
Der JIT-Compiler inlined den Aufruf von Integer.compare(); er kostet zur Laufzeit nichts.
Und hier ein Beispiel, in dem drei Student-Objekte erzeugt, in eine Liste geschrieben und anschließend sortiert werden:
public class StudentSortExample {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student(47271, "Kerrie", "Adkins"));
students.add(new Student(99319, "Aarron", "Wicks"));
students.add(new Student(11056, "Kaya", "Molina"));
Collections.sort(students);
System.out.println("students = " + students);
}
}
Wie erwartet, werden die Student-Objekte nach Matrikelnummer sortiert ausgegeben (die Zeilenumbrüche habe ich der Übersicht halber manuell eingefügt):
students = [Student{id=11056, firstName='Kaya', lastName='Molina'},
Student{id=47271, firstName='Kerrie', lastName='Adkins'},
Student{id=99319, firstName='Aarron', lastName='Wicks'}]
Kommen wir zurück zum String, den wir im nächsten Schritt nicht alphabetisch, sondern nach Länge sortieren wollen.
Das Java-Comparator-Interface
Um zwei Objekte nach einer anderen als ihrer natürlichen Ordnung zu sortieren (oder um Objekte von Klassen zu sortieren, die Comparable nicht implementieren), verwenden wir das java.util.Comparator-Interface.
Dieses definiert die Methode compare(T o1, T o2), mit der die zwei übergebenen Objekte verglichen werden. Die Methode hat analog zur compareTo()-Methode folgende Rückgabewerte:
- einen Wert kleiner als 0, wenn
o1kleiner ist also2 - 0, wenn
o1undo2in dieser Ordnung gleichrangig sind – das heißt nicht, dasso1.equals(o2)trueergibt: Der Längen-Comparator im nächsten Abschnitt liefert für „Mary“ und „John“ 0 - einen Wert größer als 0, wenn
o1größer ist also2
Java-Comparator-Beispiel: Strings nach Länge sortieren
Einen Comparator, der Strings nach ihrer Länge vergleicht, würden wir wie folgt implementieren:
public class StringLengthComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
if (o1.length() < o2.length()) {
return -1;
} else if (o1.length() == o2.length()) {
return 0;
} else {
return 1;
}
}
}
Auch hier können wir den Code mit Hilfe des ternären Operators wieder auf eine Zeile komprimieren:
@Override
public int compare(String o1, String o2) {
return o1.length() < o2.length() ? -1 : (o1.length() == o2.length() ? 0 : 1);
}
Eingesetzt wird der StringLengthComparator beispielsweise wie folgt:
public class NameSortByLengthExample {
public static void main(String[] args) {
String[] names = {"Mary", "James", "Patricia", "John", "Jennifer", "Robert"};
Arrays.sort(names, new StringLengthComparator());
System.out.println(Arrays.toString(names));
}
}
Die Namen werden nun nicht mehr alphabetisch sortiert, sondern aufsteigend nach Länge:
[Mary, John, James, Robert, Patricia, Jennifer]
Wie erstellt man einen Comparator?
Bis Java 7 konnte man einen Comparator ausschließlich – wie im Beispiel oben gezeigt – durch Implementierung des Comparator-Interfaces erstellen.
Seit Java 8 kann man einen Comparator auch als Lambda notieren oder ihn – wie du gleich sehen wirst – mit Hilfe der Methoden Comparator.comparing(), thenComparing() und reversed() erzeugen.
Comparator als öffentliche Klasse
Am Beispiel StringLengthComparator haben wir bereits die erste Variante kennengelernt: Wir schreiben eine öffentliche Klasse und übergeben der Sortiermethode eine Instanz davon:
Arrays.sort(names, new StringLengthComparator());
Wenn wir an mehreren Stellen nach Stringlänge sortieren wollen, können wir auch eine Konstante extrahieren:
private static final StringLengthComparator STRING_LENGTH_COMPARATOR =
new StringLengthComparator();
Alternativ könnten wir ein Singleton definieren:
public class StringLengthComparator implements Comparator<String> {
public static final StringLengthComparator INSTANCE = new StringLengthComparator();
private StringLengthComparator() {}
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
}
Eine öffentliche Klasse gibt uns auch die Möglichkeit, das Sortierverhalten über Konstruktor-Parameter zu steuern. Z. B. könnten wir die Sortierreihenfolge konfigurierbar machen:
public class StringLengthComparator implements Comparator<String> {
public static final StringLengthComparator ASC = new StringLengthComparator(true);
public static final StringLengthComparator DESC = new StringLengthComparator(false);
private final boolean ascending;
private StringLengthComparator(boolean ascending) {
this.ascending = ascending;
}
@Override
public int compare(String o1, String o2) {
int result = Integer.compare(o1.length(), o2.length());
return ascending ? result : -result;
}
}
Diesen Comparator würden wir beispielsweise wie folgt einsetzen, um unsere Namensliste absteigend zu sortieren:
Arrays.sort(names, StringLengthComparator.DESC);
Eine öffentliche Klasse lässt uns also die meisten Freiheiten bei der Definition unseres Comparators – für die Sortierrichtung brauchen wir sie seit Java 8 nicht mehr, wie der Abschnitt „Absteigend sortieren mit reversed()“ zeigt.
Comparator als anonyme Klasse
Benötigen wir einen Comparator nur an einer einzigen Stelle, können wir ihn auch als anonyme Klasse definieren.
Mit dem folgenden Code beispielsweise sortieren wir unsere Student:innen nach Nachnamen:
students.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getLastName().compareTo(o2.getLastName());
}
});
Die Methode List.sort() gibt es ebenfalls erst seit Java 8; davor schreibt man Collections.sort(students, comparator).
Da es Nachnamen gibt, die häufiger vorkommen, sollten wir nach Nachnamen und nach Vornamen sortieren. Dies tun wir, indem wir zunächst prüfen, ob die Nachnamen gleich sind. Wenn das der Fall ist, vergleichen wir auch die Vornamen:
students.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int result = o1.getLastName().compareTo(o2.getLastName());
if (result == 0) {
result = o1.getFirstName().compareTo(o2.getFirstName());
}
return result;
}
});
In beiden Fällen wird uns eine moderne IDE wie IntelliJ darauf hinweisen, dass das ab Java 8 auch eleganter geht (und uns im besten Fall auch gleich anbieten, den Code zu refactoren):

Was dabei herauskommt, erfährst du im nächsten Abschnitt.
Java-Comparator als Lambda notieren
Ab Java 8 können wir statt der anonymen Klasse nämlich auch ein Lambda verwenden. Das Sortieren nach Nachname erfolgt dann wie folgt:
students.sort((o1, o2) -> o1.getLastName().compareTo(o2.getLastName()));
Auch das Sortieren nach Nach- und Vorname wird durch die Lambda-Notation kürzer:
students.sort((o1, o2) -> {
int result = o1.getLastName().compareTo(o2.getLastName());
if (result == 0) {
result = o1.getFirstName().compareTo(o2.getFirstName());
}
return result;
});
Kürzer geht es mit der Methode, die ich im folgenden Abschnitt zeige. Auch den Schritt dorthin bietet uns eine moderne IDE an:

Comparator erzeugen mit Comparator.comparing()
Am kürzesten wird ein Comparator mit Comparator.comparing() und den darauf aufrufbaren Methoden thenComparing() und reversed() – ebenfalls seit Java 8, samt Varianten für die primitiven Datentypen int, long und double.
Um die Student:innen nach Nachnamen zu sortieren, schreiben wir folgendes:
students.sort(Comparator.comparing(Student::getLastName));
Wir übergeben also lediglich eine Referenz auf die Methode, die den Wert liefert, nach dem sortiert werden soll. Der Javadoc nennt diesen Wert den sogenannten Schlüssel (englisch „sort key“) und den Parameter von comparing() entsprechend keyExtractor – der Begriff kommt weiter unten bei den null-Werten wieder.
Nach mehreren Kriterien sortieren mit thenComparing()
Nach Nach- und Vornamen sortieren wir wie folgt:
students.sort(Comparator.comparing(Student::getLastName)
.thenComparing(Student::getFirstName));
Diese Schreibweise macht es uns einfach, auch den Fall abzudecken, dass zwei Student:innen nicht nur den gleichen Nach-, sondern auch den gleichen Vornamen haben. Um sie in diesem Fall zusätzlich nach ID zu sortieren, müssen wir lediglich ein thenComparingInt() hinzufügen:
students.sort(Comparator.comparing(Student::getLastName)
.thenComparing(Student::getFirstName)
.thenComparingInt(Student::getId));
Comparator.comparing() und die damit aufgebauten Comparator-Ketten machen den Code nicht nur kürzer, sondern auch aussagekräftiger: Du siehst auf den ersten Blick, wonach sortiert wird.
Absteigend sortieren mit reversed()
Absteigend sortierst du mit reversed():
students.sort(Comparator.comparing(Student::getLastName).reversed());
reversed() dreht die gesamte Kette um, die vor ihm steht – bei comparing(…).thenComparing(…).reversed() also beide Kriterien. Der StringLengthComparator mit den Konstanten ASC und DESC aus dem Abschnitt „Comparator als öffentliche Klasse“ schrumpft damit auf eine Zeile:
Arrays.sort(names, Comparator.comparingInt(String::length).reversed());
Für die natürliche Ordnung und ihre Umkehrung gibt es Comparator.naturalOrder() und Comparator.reverseOrder().
null-Werte sortieren mit nullsFirst() und nullsLast()
Comparator.comparing() wirft eine NullPointerException, sobald der Schlüssel eines Elements null ist – im Beispiel also ein fehlender Nachname. Dagegen hilft der zweite Parameter von comparing(), ein Comparator für den Schlüssel – hier Comparator.nullsLast(), der null ans Ende sortiert und alle anderen Schlüssel an den übergebenen Comparator weiterreicht:
students.sort(Comparator.comparing(Student::getLastName,
Comparator.nullsLast(Comparator.naturalOrder())));
Objekte ohne Nachnamen landen damit am Ende, mit nullsFirst() am Anfang. Ein Comparator.nullsLast(comparator) um die ganze Kette behandelt dagegen null als Element der students-Liste, nicht als Schlüssel.
Streams sortieren mit Comparator.comparing()
Dieselbe Comparator-Kette sortiert auch einen Stream:
List<Student> sorted = students.stream()
.sorted(Comparator.comparing(Student::getLastName))
.toList();
Comparable vs. Comparator – Zusammenfassung
Wir haben im Laufe des Artikels die Interfaces java.lang.Comparable und java.util.Comparator kennengelernt. Die Tabelle stellt sie gegenüber:
Comparable | Comparator | |
|---|---|---|
| Package | java.lang | java.util |
| Methode | compareTo(T o) | compare(T o1, T o2) |
| Implementiert von | der Klasse selbst | einem eigenen Objekt |
| Ordnungen je Klasse | genau eine, die natürliche Ordnung | beliebig viele |
| Erzeugen | implements Comparable<T> | Klasse, anonyme Klasse, Lambda oder Comparator.comparing() |
| Typischer Aufruf | Collections.sort(list) | list.sort(comparator) |
Mit einem Comparator können wir außerdem Objekte sortieren, die das Comparable-Interface nicht implementieren, also keine natürliche Ordnung haben.
Seit Java 8 lassen sich Comparatoren in einer Zeile definieren, wie z. B. in students.sort(Comparator.comparing(Student::getLastName)).
Meine Empfehlung: Comparable nur für die eine offensichtliche Ordnung einer Klasse – wie ID oder Zeitpunkt –, jede andere Ordnung als Comparator mit Comparator.comparing(). Und compareTo() nie per Subtraktion.
Wenn dir dieser Artikel gefallen hat, freue ich mich über eine Bewertung auf meinem ProvenExpert-Profil.




