Overview
Mit dem funktionalen Interface Comparator können wir durch den Einsatz von Lambdas sehr elegent und in hoher Code-Qualität Listen sortieren. Es ist in vielen Fällen der klassischen Sortierung mit Comparable vorzuziehen.
Wir haben bereits in einem früheren Tutorial anhand des Interfaces Comparable gezeigt, wie man Listen in Java sortieren kann. Mit den Mechanismen der Lambda-Programmierung können wir das Sortierung von Listen weiter vereinfachen und noch flexibler gestalten.
Für die Arbeit mit Lambas ist Comparable aber nicht geeignet (warum das so ist, sehen wir weiter unten). Stattdessen sollten wir zur Listensortierung mit Lambda das funktionale Interface Comparator verwenden.
Erstellen wir dazu eine Liste von Player-Objekten, die wir sortieren wollen. Die Klasse Player hat eine Integer-Member namens punkte. Der Wert wird über den Konstruktor erfasst und soll als Sortierkriterium dienen.
List<Player> spieler = new ArrayList<>();
Player player1 = new Player(50);
Player player2 = new Player(40);
Player player3 = new Player(20);
spieler.add(player1);
spieler.add(player2);
spieler.add(player3);
// Vor der Sortierung
System.out.println(spieler); // [Player{punkte=50}, Player{punkte=40}, Player{punkte=20}]
Sortieren wir die Liste zunächst einmal ohne den Einsatz einer Lambda-Funktion.
Dazu implementieren wir das Interface Comparator, indem wir die Methode compare(arg1,arg2) überschreiben und unser Sortierkriterium festlegen:
Collections.sort(spieler, new Comparator<Player>(){
@Override
public int compare(Player eins, Player zwei){
return Integer.compare(eins.getPunkte(), zwei.getPunkte());
}
});
// Nach der Sortierung
System.out.println(spieler); // [Player{punkte=20}, Player{punkte=40}, Player{punkte=50}]
Die Methode compare(arg1, arg2) des Comparator hat zwei Parameter, welche die zu vergleichenden Objekte repräsentieren. Das Sortierkriterium sind die Punkte der Spieler. Die Methode programmieren wir entsprechend so aus, dass sie entweder 0 (dann sind beide Werte gleich), einen Wert kleiner als 0 (dann ist eins < zwei) oder einen Wert größer als 0 (dann ist eins > zwei) zurückliefert.
Das Ganze verwirklichen wir über ein neues Objekt einer anonymen inneren Klasse, die das Interface Comparator<T> implementiert und das wir als Argument der Methode Collections.sort() übergeben.
Ganz klar: Den Comparator "klassisch" objektorientiert zu implementieren ist ziemlich krampfig. Da es sich beim Comparator ja um ein funktionales Interface handelt, ist er wie dafür geschaffen, alternativ mit einer Lambda-Funktion umgesetzt zu werden.
Dazu ersetzen wir einfach den Code oben mit der gesamten Lambda-Funktion als zweiten Methodenparameter der Methode Collections.sort():
Collections.sort(spieler, (p1, p2) -> Integer.compare(p1.getPunkte(), p2.getPunkte())); // sortiert!
So sieht das doch schon wesentlich besser aus! Wir haben nämlich den Code für das Sortieren einer Liste funktional super stark reduziert und unnötig aufgeblähten Boilerplate-Code rausgeschmissen.
Natürlich lässt wir unsere Lambda-Implementierung auch mit einer entsprechenden Comparator-Variablen referenzieren. Die Variable können wir dann einfach der Collections.sort() Methode als Argument übergeben:
Comparator<Player> c = (x, y) -> Integer.compare(x.getPunkte(), y.getPunkte());
Collections.sort(spieler, c);
Wir können den Lambda-Ausdruck durch eine Methodenreferenz ersetzen und den Code damit sogar noch kürzer machen:
Comparator<Player> c2 = Comparator.comparingInt(Player::getPunkte);
Collections.sort(spieler, c2);
Für das Sortieren von Listen haben wir jetzt schon insgesamt zwei unterschiedliche Interfaces zum Sortieren von Listen kennengelernt: Comparable und in diesem Tutorial den Comparator. Beide Interfaces sind sogar funktionale Interfaces - beide eignen sich daher technisch gesehen für eine Verwendung mit Lambdas.
Was spricht für den Einsatz von Comparator und gegen Comparable?
Es gibt Situationen, in denen man Objekte sortieren möchte, die das Comparable-Interface nicht implementiert haben - Zur Erinnerung: das Sortieren mit Comparable funktioniert nur, wenn es in der Klasse des abgeleiteten Objekts implementiert wurde.
Erstens: Wir haben nicht immer Zugriff auf den Sourcecode der Klassen, deren Objekte wir sortieren möchten. In diesem Fall ist der Einsatz von Comparable unmöglich.
Zweitens: Wir möchten den Sourcecode der Klassen, deren Objekte wir sortieren möchten, nicht mit zusätzlichem Code aufladen.
Drittens: Wir möchten Objekte flexibel nach unterschiedlichen Sortierkriterien sortieren - Erinnerung: Bei Comparable wird das Suchkriterium über die Methode compareTo() festgelegt und diese kann nur einmal implementiert werden.
Viertens: Nur Comparator eignet sich für den Lambda-Einsatz. Dagegen machen Lambdas bei Comparable keinen Sinn - denn Comparable muss innerhalb der Klasse des vergleichenden Objekts implementiert werden.
Natürlich heißt das alles nicht, dass wir uns nur noch auf Comparator verlassen und Comparable für unsere Programmieraufgaben nie mehr in Betracht ziehen. Denn auch wenn Comparator massive Vorteile hat, so ist es doch von Situation zu Situation zu entscheiden, welches das geeignetere Interface zum Sortieren von Listen ist.
Java Basics
[Java einrichten] [Variablen] [Primitive Datentypen] [Operatoren] [if else] [switch-case] [Arrays] [Schleifen]
Objektorientierung
[Einstieg] [Variablen ] [Konstruktor] [Methoden] [Rekursion] [Statische Member] [Initializer] [Pass-by-value] [Objektsammlungen] [Objektinteraktion] [Objekte löschen]
Klassenbibliothek
[Allgemeines] [String ] [Math] [Wrapper] [Scanner] [java.util.Arrays] [Date-Time-API]
Vererbung
[Einstieg Vererbung] [Konstruktoren bei Vererbung ] [Der protected Zugriffsmodifikator] [Abstrakte Klassen und Methoden] [Polymorphie in Java] [Typumwandlung] [Die Klasse Object] [Die toString()-Methode] [Objekte vergleichen] [Was ist ein Interface?]