JAVA Tutorial #67

Das funktionale Interface Comparator

2024-05-23 | credits: Muzamil@stock.adobe

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. 

Das Interface Comparator

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}]  

Comparator ohne Lambda

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. 

Comparator mit Lambda implementieren

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. 

Lamba in Variable speichern

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);

Methodenreferenz

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);

Comparator vs. Comparable

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.

Fazit

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.  

Werbung

Java lernen

Werde zum Java Profi!

PHP Lernen

Lerne serverbasierte Programmierung

JavaScript lernen

Skille dein Webcoding

FALCONBYTE.NET

Handmade with 🖤️

© 2018-2023 Stefan E. Heller

Impressum | Datenschutz | Changelog

Falconbyte Youtube Falconbyte GitHub facebook programmieren lernen twitter programmieren lernen discord programmieren lernen