Java 8 simplifie les Comparators

L’API de Collections de Java apporte la notion d’ordre des objets, soit via l’interface Comparable<T> qui définit l’ordre naturel, soit via l’interface Comparator<T> qui permet de définir des règles d’ordonnancements diverses.

La majeur partie des classes et méthodes qui nécessite un ordonnancement utilisent donc un Comparator<T>.
Bien que sur le principe cela ne soit pas très complexe en soit, cela restait relativement verbeux à écrire et source de bug ou comportement étrange en cas de petite erreur…

Java 8 va revisiter tout cela, et il y a de forte chance que vous n’ayez plus à écrire le Comparator par vous-même…

Imaginons une classe « Car« , représentant une voiture et ses différentes caractéristiques avec leurs getters (getManufacturer(), getModel(), getYear(), getHorsepower(), …)

L’implémentation d’un Comparator sur ces 4 critères ressemblerait à ceci :

Comparator<Car> comparator = new Comparator<Car>() {
    @Override
    public int compare(Car c1, Car c2) {
        int diff = c1.getManufacturer().compareTo(c2.getManufacturer());
        if (diff != 0) {
            return diff;
        }
        diff = c1.getModel().compareTo(c2.getModel());
        if (diff != 0) {
            return diff;
        }
        diff = Integer.compare(c1.getYear(), c2.getYear());
        if (diff != 0) {
            return diff;
        }
        diff = Integer.compare(c1.getHorsepower(), c2.getHorsepower());
        return diff;
    }
};

Ce n’est pas bien méchant en soit… mais c’est juste une comparaison basé sur 4 champs.
Cela peut vite devenir plus long et barbant, avec du code répétitif un peu lourdingue.

Heureusement, à partir de Java 8 l’interface Comparator intègrera une méthode static comparing() permettant de créer un comparateur basé sur un champ, en lui passant une expression lambda permettant d’extraire le champ à comparer.
Par exemple pour créer un Comparator basé sur le modèle de voiture on pourra utiliser directement :

Comparator<Car> comparator = Comparator.comparing((Car c) -> c.getModel());

Toutefois ce genre d’expression lambda pouvant être remplacée par une référence de méthode, il est plus simple et plus lisible d’utiliser cette forme :

Comparator<Car> comparator = Comparator.comparing(Car::getModel);

On obtient ainsi un Comparator qui se basera sur la valeur de retour de getModel() pour ordonner les éléments.

Mais ce ne s’arrête pas là, les Comparators s’enrichissent également de nouvelles méthodes d’instances via les méthodes par défaut. On dispose ainsi en particulier de méthode thenComparing() permettant d’enchainer avec un autre comparateur.
On peut ainsi réécrire notre comparateur initial en quelques lignes :

Comparator<Car> comparator = Comparator.comparing(Car::getManufacturer)
    .thenComparing(Car::getModel)
    .thenComparingInt(Car::getYear)
    .thenComparingInt(Car::getHorsepower);

On peut également affecter un Comparateur à chaque champs, dans le cas où il ne serait pas « Comparable » ou que l’on souhaiterait utiliser un autre ordre de trie :

    // Comparaison selon le modèle, en ignorant la case :
    .thenComparing(Car::getModel, String::compareToIgnoreCase)

    // Comparaison selon le modèle, selon les règles françaises :
    .thenComparing(Car::getModel, Collator.getInstance(Locale.FRANCE))

    // Comparaison selon le modèle, mais en ordre inverse
    .thenComparing(Car::getModel, Comparator.reverseOrder())

Bref de quoi traiter une grande majorité de cas facilement et rapidement !

Laisser un commentaire