Vous avez sûrement déjà dû développer un comparateur, soit en passant par l’interface Comparable
soit par Comparator
.
Prenons comme exemple la classe suivante :
class Person { String firstName, lastName; String getFirstName() { return firstName; } String getLastName() { return lastName; } }
L’API de Comparator
Rappelons que l’interface Comparable<T>
est définie ainsi :
int compareTo(T t1, T t2);
Cette méthode retourne un entier :
- inférieur à 0 si t1 est plus petit que t2
- égal à 0 si t1 est égal à t2
- supérieur à 0 si t1 est plus grand que t2
Le comparateur avec l’API classique
Pour déclarer un comparateur classique qui trie des personnes, par exemple, par leur nom puis par leur prénom, nous obtenons un code qui ressemble à ça :
class Person implements Comparable<Person> { ... int compareTo(Person p1, Person p2) { int firstNameCompare = p1.getFirstName().compareTo(p2.getFirstName()); if( firstNameCompare == 0 ) { return p1.getLastName().compareTo(p2.getLastName()); } else { return firstNameCompare; } } }
C’est assez verbeux, pas forcement facile à comprendre et ça ne gère que deux champs considérés comme non nuls.
Si on imagine une classe avec plus de champs et si on doit en plus gérer les valeurs nulles, alors le code du comparateur devient beaucoup plus complexe à écrire, comprendre et maintenir.
Le comparateur introduit en Java 8 avec l’API fluent
La classe Comparator a été améliorée en Java 8 pour faciliter la construction de comparateurs de manière déclarative avec une API fluent.
Voyons comment on peut réécrire le comparateur précédant avec la nouvelle API de Java 8 :
Comparator<Person> personComparator = Comparator .comparing(Person::getFirstName) .thenComparing(Person::getLastName);
Par défaut, les méthodes comparing
et thenComparing
extraient le champ à comparer et utilisent la méthode compareTo
de la classe du champ extrait pour faire la comparaison.
Ces méthodes sont surchargées pour permettre également la séparation de la fonction d’extraction, de la fonction de comparaison elle-même.
Ceci permet de faire des comparaisons avec une logique différente de celle déclarée par défaut dans la classe (dans la méthode compareTo
) :
Imaginons que l’on veuille trier les personnes par nom puis par prénom tout en positionnant en premier les personnes sans nom de famille et en dernier les personnes sans prénom.
Pour simplifier le code, considérons que l’on a un import statique des fonctions présentes dans Comparator
.
On aura donc :
comparing(Person::getLastName, nullsFirst(String::compareTo)) .thenComparing(Person::getFirstName, nullsLast(String::compareTo)
Dans cet exemple, la fonction d’extraction est Person::getFirstName
tandis que la fonction de comparaison est un comparateur dérivé de nullLast (valeurs nulles a la fin) et si les deux valeurs ne sont pas nulles alors on utilise String::compareTo
pour la comparaison.
comparing
/thenComparing
au bon endroit selon la priorité désirée pour le champ d’âge ou date de naissance, tandis qu’avec la méthode classique le code devient tout de suite plus complexe et difficile à gérer.Ces fonctionnalités sont suffisantes pour déclarer la plupart des scénarios de comparaison.
Comparator
permet de gérer facilement la plupart des scénarios de comparaison et cela rend l’écriture d’un Comparator
beaucoup plus facile.