Comparateur et comparable en Java

1. Introduction

Les comparaisons en Java sont assez faciles - jusqu'à ce qu'elles ne le soient pas.

Lorsque vous travaillez avec des types personnalisés ou que vous essayez de comparer des objets qui ne sont pas directement comparables, nous devons utiliser une stratégie de comparaison. Nous pouvons en construire un simplement, mais en utilisant les interfaces Comparator ou Comparable .

2. Configuration de l'exemple

Prenons un exemple d'une équipe de football - où nous voulons aligner les joueurs en fonction de leur classement.

Nous allons commencer par créer une classe Player simple :

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

Ensuite, créons une classe PlayerSorter pour créer notre collection et tentons de la trier à l'aide de Collections.sort :

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

Ici, comme prévu, cela entraîne une erreur de compilation:

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

Comprenons ce que nous avons fait de mal ici.

3. Comparable

Comme son nom l'indique, Comparable est une interface définissant une stratégie de comparaison d'un objet avec d'autres objets du même type. C'est ce qu'on appelle «l'ordre naturel» de la classe.

Par conséquent, afin de pouvoir trier - nous devons définir notre objet Player comme comparable en implémentant l' interface Comparable :

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

L'ordre de tri est décidé par la valeur de retour de la méthode compareTo () . Le Integer.compare (x, y) renvoie -1 si x est inférieur à y , renvoie 0 si elles sont égales, et les rendements 1 sinon.

La méthode renvoie un nombre indiquant si l'objet comparé est inférieur, égal ou supérieur à l'objet passé en tant qu'argument.

Enfin, lorsque nous exécutons notre PlayerSorter maintenant, nous pouvons voir nos joueurs triés par leur classement:

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

Maintenant que nous avons une compréhension claire de l'ordre naturel avec Comparable , voyons comment nous pouvons utiliser d'autres types de commande, de manière plus flexible que d'implémenter directement une interface.

4. Comparateur

L' interface Comparator définit une méthode compare (arg1, arg2) avec deux arguments qui représentent des objets comparés et fonctionne de manière similaire à la méthode Comparable.compareTo () .

4.1. Créer des comparateurs

Pour créer un comparateur, nous devons implémenter l' interface Comparator .

Dans notre premier exemple, nous allons créer un comparateur pour utiliser l' attribut de classement de Player pour trier les joueurs:

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

De même, nous pouvons créer un comparateur pour utiliser l' attribut age de Player pour trier les joueurs:

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

4.2. Comparateurs en action

Pour démontrer le concept, modifions notre PlayerSorter en introduisant un deuxième argument dans la méthode Collections.sort qui est en fait l'instance de Comparator que nous voulons utiliser.

En utilisant cette approche, nous pouvons remplacer l'ordre naturel :

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

Maintenant, exécutons notre PlayerRankingSorter pour voir le résultat:

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

Si nous voulons un ordre de tri différent, il suffit de changer le comparateur que nous utilisons:

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

Maintenant, lorsque nous exécutons notre PlayerAgeSorter , nous pouvons voir un ordre de tri différent par âge:

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

4.3. Comparateurs Java 8

Java 8 fournit de nouvelles façons de définir des comparateurs à l'aide d'expressions lambda et de la méthode de fabrique statique compare () .

Voyons un exemple rapide d'utilisation d'une expression lambda pour créer un comparateur :

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

La méthode Comparator.comparing prend une méthode de calcul de la propriété qui sera utilisée pour comparer les éléments et renvoie une instance de Comparator correspondante :

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

Vous pouvez explorer en profondeur les fonctionnalités de Java 8 dans notre guide de comparaison Java 8.

5. Comparateur vs comparable

L' interface Comparable est un bon choix lorsqu'elle est utilisée pour définir l'ordre par défaut ou, en d'autres termes, si c'est le principal moyen de comparer des objets.

Ensuite, nous devons nous demander pourquoi utiliser un comparateur si nous avons déjà Comparable ?

Il y a plusieurs raisons pour lesquelles:

  • 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.

Et, comme d'habitude, le code source peut être trouvé sur GitHub.