Komparator und Vergleichbar in Java

1. Einleitung

Vergleiche in Java sind recht einfach - bis sie es nicht sind.

Wenn Sie mit benutzerdefinierten Typen arbeiten oder versuchen, Objekte zu vergleichen, die nicht direkt vergleichbar sind, müssen Sie eine Vergleichsstrategie verwenden. Wir können eine einfach erstellen, aber die Comparator- oder Comparable- Schnittstellen verwenden.

2. Einrichten des Beispiels

Nehmen wir ein Beispiel einer Fußballmannschaft, bei der wir die Spieler nach ihrer Rangliste aufstellen möchten.

Wir beginnen mit der Erstellung einer einfachen Spielerklasse :

public class Player { private int ranking; private String name; private int age; // constructor, getters, setters }

Als Nächstes erstellen wir eine PlayerSorter- Klasse, um unsere Sammlung zu erstellen, und versuchen, sie mit Collections.sort zu sortieren :

public static void main(String[] args) { List footballTeam = new ArrayList(); Player player1 = new Player(59, "John", 20); Player player2 = new Player(67, "Roger", 22); Player player3 = new Player(45, "Steven", 24); footballTeam.add(player1); footballTeam.add(player2); footballTeam.add(player3); System.out.println("Before Sorting : " + footballTeam); Collections.sort(footballTeam); System.out.println("After Sorting : " + footballTeam); } 

Hier führt dies erwartungsgemäß zu einem Fehler bei der Kompilierung:

The method sort(List) in the type Collections is not applicable for the arguments (ArrayList)

Lassen Sie uns verstehen, was wir hier falsch gemacht haben.

3. Vergleichbar

Wie der Name schon sagt, ist Comparable eine Schnittstelle, die eine Strategie zum Vergleichen eines Objekts mit anderen Objekten desselben Typs definiert. Dies wird als "natürliche Ordnung" der Klasse bezeichnet.

Dementsprechend wird , um zu sortieren zu können - wir müssen unsere definieren Spielerobjekt als vergleichbar mit der Umsetzung Vergleichbare Schnittstelle:

public class Player implements Comparable { // same as before @Override public int compareTo(Player otherPlayer) { return Integer.compare(getRanking(), otherPlayer.getRanking()); } } 

Die Sortierreihenfolge wird durch den Rückgabewert der compareTo () -Methode bestimmt. Die Integer.compare (x, y) -1 , wenn x kleiner als y , 0 zurück , wenn sie gleich sind, und gibt 1 zurück , andernfalls.

Die Methode gibt eine Zahl zurück, die angibt, ob das zu vergleichende Objekt kleiner, gleich oder größer als das als Argument übergebene Objekt ist.

Wenn wir jetzt unseren PlayerSorter ausführen , können wir unsere Spieler nach ihrer Rangfolge sortiert sehen:

Before Sorting : [John, Roger, Steven] After Sorting : [Steven, John, Roger]

Nachdem wir nun ein klares Verständnis der natürlichen Reihenfolge mit Comparable haben , wollen wir sehen, wie wir andere Arten der Reihenfolge flexibler verwenden können als die direkte Implementierung einer Schnittstelle.

4. Komparator

Die Comparator- Schnittstelle definiert eine compare- Methode (arg1, arg2) mit zwei Argumenten, die verglichene Objekte darstellen, und funktioniert ähnlich wie die Comparable.compareTo () -Methode.

4.1. Komparatoren erstellen

Um einen Komparator zu erstellen , müssen wir die Komparator- Schnittstelle implementieren .

In unserem ersten Beispiel erstellen wir einen Komparator , der das Rangattribut des Spielers verwendet , um die Spieler zu sortieren:

public class PlayerRankingComparator implements Comparator { @Override public int compare(Player firstPlayer, Player secondPlayer) { return Integer.compare(firstPlayer.getRanking(), secondPlayer.getRanking()); } }

In ähnlicher Weise können wir schaffen Vergleicher zu verwenden , um das Alte Attribut Spieler , die Spieler zu sortieren:

public class PlayerAgeComparator implements Comparator { @Override public int compare(Player firstPlayer, Player secondPlayer) { return Integer.compare(firstPlayer.getAge(), secondPlayer.getAge()); } }

4.2. Komparatoren in Aktion

Um das Konzept zu demonstrieren, ändern wir unseren PlayerSorter, indem wir ein zweites Argument in die Collections.sort-Methode einfügen , die eigentlich die Instanz von Comparator ist, die wir verwenden möchten.

Mit diesem Ansatz können wir die natürliche Reihenfolge überschreiben :

PlayerRankingComparator playerComparator = new PlayerRankingComparator(); Collections.sort(footballTeam, playerComparator); 

Lassen Sie uns nun unseren PlayerRankingSorter ausführen, um das Ergebnis zu sehen:

Before Sorting : [John, Roger, Steven] After Sorting by ranking : [Steven, John, Roger]

Wenn wir eine andere Sortierreihenfolge wünschen, müssen wir nur den von uns verwendeten Komparator ändern :

PlayerAgeComparator playerComparator = new PlayerAgeComparator(); Collections.sort(footballTeam, playerComparator);

Wenn wir jetzt unseren PlayerAgeSorter ausführen , sehen wir eine andere Sortierreihenfolge nach Alter:

Before Sorting : [John, Roger, Steven] After Sorting by age : [Roger, John, Steven]

4.3. Java 8- Komparatoren

Java 8 bietet neue Möglichkeiten zum Definieren von Komparatoren mithilfe von Lambda-Ausdrücken und der statischen Factory-Methode compare () .

Sehen wir uns ein kurzes Beispiel für die Verwendung eines Lambda-Ausdrucks zum Erstellen eines Komparators an :

Comparator byRanking = (Player player1, Player player2) -> Integer.compare(player1.getRanking(), player2.getRanking());

Die Comparator.comparing- Methode berechnet anhand einer Methode die Eigenschaft, die zum Vergleichen von Elementen verwendet wird, und gibt eine übereinstimmende Comparator- Instanz zurück:

Comparator byRanking = Comparator .comparing(Player::getRanking); Comparator byAge = Comparator .comparing(Player::getAge);

Sie können die Java 8-Funktionalität in unserem Java 8 Comparator.comparing-Handbuch ausführlich untersuchen.

5. Komparator vs Vergleichbar

Die Schnittstelle " Vergleichbar " ist eine gute Wahl , wenn Sie die Standardreihenfolge definieren oder mit anderen Worten, dies ist die Hauptmethode zum Vergleichen von Objekten.

Dann müssen wir uns fragen, warum wir einen Komparator verwenden, wenn wir bereits Comparable haben .

Dafür gibt es mehrere Gründe:

  • Sometimes, we can't modify the source code of the class whose objects we want to sort, thus making the use of Comparable impossible
  • Using Comparators allows us to avoid adding additional code to our domain classes
  • We can define multiple different comparison strategies which isn't possible when using Comparable

6. Avoiding the Subtraction Trick

Over the course of this tutorial, we used the Integer.compare() method to compare two integers. One might argue that we should use this clever one-liner instead:

Comparator comparator = (p1, p2) -> p1.getRanking() - p2.getRanking();

Although it's much more concise compared to other solutions, it can be a victim of integer overflows in Java:

Player player1 = new Player(59, "John", Integer.MAX_VALUE); Player player2 = new Player(67, "Roger", -1); List players = Arrays.asList(player1, player2); players.sort(comparator);

Since -1 is much less than the Integer.MAX_VALUE, “Roger” should come before the “John” in the sorted collection. However, due to integer overflow, the “Integer.MAX_VALUE – (-1)” will be less than zero. So, based on the Comparator/Comparable contract, the Integer.MAX_VALUE is less than -1, which is obviously incorrect.

Hence, despite what we expected, “John” comes before the “Roger” in the sorted collection:

assertEquals("John", players.get(0).getName()); assertEquals("Roger", players.get(1).getName());

7. Conclusion

In this tutorial, we explored the Comparable and Comparator interfaces and discussed the differences between them.

To understand more advanced topics of sorting, check out our other articles such as Java 8 Comparator, Java 8 Comparison with Lambdas.

Und wie immer ist der Quellcode auf GitHub zu finden.