Trier efficacement les objets en Java – 2ème partie : Guava

Le tri d'objets en Java s'effectue à l'aide de méthodes et de classes de comparaisons.

Cependant, l'écriture de ces méthodes peut vite devenir fastidieuse et être source d'erreurs lorsque les objets à comparer ont beaucoup de propriétés à tester.

Voyons comment la bibliothèque Guava de Google peut nous aider à simplifier l'écriture de ces méthodes.

Guava : Ordering et ComparisonChain

Nous verrons ainsi l'utilisation des classes Ordering et ComparisonChain de Guava.

La classe Ordering

La classe Ordering contient des méthodes qui permettent de définir plus simplement le tri d'une collection d'éléments.
Elle contient également des méthodes qui permettent de lancer le tri d'une collection d'éléments.

Ordering implémente Comparator

Ordering implémente l'interface Comparator ce qui permet d'utiliser une instance Ordering de la même façon qu'une instance Comparator.

Définition d'une instance de Ordering

Nous pouvons définir une instance Ordering des deux manières suivantes :

Définition d'un Ordering à partir d'un Comparator existant

Nous pouvons définir un Ordering à partir d'un Comparator existant.

Ordering ordering = Ordering.from(comparator);

Ceci permet d'étendre le comportement de ce Comparator pour modifier le tri des éléments, comme par exemple inverser l'ordre du tri.

 

Définition d'un Ordering à l'aide d'une méthode de tri par défaut

Nous pouvons utiliser les ordres de tri fournis par la classe Ordering :

  • ordre naturel : la comparaison des instances repose sur la méthode compareTo de chaque instance. Ceci implique obligatoirement que les objets triés implémentent l'interface Comparable et la méthode compareTo.
 Ordering ordering = Ordering.natural();

 

  • utilisation de toString : la comparaison des instances repose sur la méthode toString de chaque instance.
 Ordering ordering = Ordering.usingToString();

 

  • tri selon les références des instances : la comparaison des instances repose sur la comparaison des références des instances.
 Ordering ordering = Ordering.arbitrary();

 

 

Etendre le comportement du tri

Ordering contient des méthodes qui permettent d'étendre le comportement de base de la comparaison.

Par exemple, nous pouvons inverser l'ordre du tri, gérer les instances non définies pour éviter les exceptions NullPointerException.

Inverser l'ordre

Pour inverser l'ordre, nous ajoutons l'appel à la méthode reverse :

Ordering ordering = Ordering.from(comparator).reverse();

 

Gérer le cas des objets non définis

Dans le cas où un des objets à comparer est nul, par défaut l'exception NullPointerException est levée.

Cependant ce cas peut être géré via l'appel à la méthode nullsFirst ou nullsLast qui indique que les instances non définies se retrouvent respectivement au début ou à la fin de la liste.

// Les instances nulles ne sont pas gérées et un __NullPointerException__ peut être levé
Ordering.from(comparator);

// Les instances nulles sont gérées et se retrouvent au début de la liste
Ordering.from(comparator).nullsFirst();

 

Si nous voulons les valeurs nulles à la fin, nous avons également l'expression suivante :

Ordering.natural().nullsLast();

 

Indiquer la propriété de l'objet sur laquelle effectuer le tri

La méthode onResultOf permet d'indiquer sur quelle propriété de l'objet effectuer le tri.

Cette méthode prend en paramètre une instance de Function qui est une autre classe fournit par la bibliothèque et qui permet de retourner une valeur à partir de l'objet passé en paramètre.

Par exemple, dans le cas d'un livre disposant de la propriété auteur, nous pouvons définir le tri sur l'ordre naturel selon cette propriété auteur :

final Ordering<Livre> livreOrdering =
    Ordering.natural().nullsFirst().onResultOf(new Function<Livre, String>() {
        public String apply(final Livre input)
        {
            return input.getAuteur();
        }
    });

final List<Livre> livresResultat = livreOrdering.sortedCopy(livres);

La méthode apply de la classe Function retourne ici la propriété auteur à partir de l'instance de Livre. L'auteur servira à trier les livres. De plus la méthode nullsFirst permet de gérer les cas où la collection contient des livres non définis.

Lancer le tri des éléments

Trier les éléments

Nous pouvons maintenant trier nos livres à l'aide notre nouveau comparateur, soit à l'aide Collections.sort, soit à l'aide de Ordering.sortedCopy.

// Livres à trier
List<Livre> livres = getLivres();

// Comparaison de livres
LivreComparator livreComparator = 
    Ordering.from(new LivreComparator()).nullFirst();

// Trier les livres
List<Livre> livreTries = 
    Ordering.from(livreComparator).sortedCopy(livres);

 

Exemples d'utilisation de la classe Ordering

Nous pouvons ainsi avoir les déclarations suivantes :

Tri naturel sur les auteurs d'un livre :

Nous trions les livres suivant un Comparator sur les livres en inversant l'ordre et gérant le cas des livres non définis :

final List<Livre> livresResultat =
            Ordering.from(livreComparator).reverse().nullsFirst().sortedCopy(livres);

 

Collection triée de livres

Nous définissons une collection triée de livres avec gestion des cas des instances nulles :

Set<Livre> livres = new TreeSet<Livre>(
            Ordering.from(livreComparator).nullFirst());

Conclusion sur l'utilisation d'un Ordering

La classe Ordering propose d'autres fonctionnalités non détaillées ici qui permettent d'étendre davantage le comportement

 

ComparisonChain

La classe ComparisonChain facilite l'écriture des méthodes de comparaison de type compareTo et des Comparator.

En effet, elle permet de tester à la suite chacune des propriétés des objets à comparer.

Elle s'utilise de la manière suivante :

  1. on appelle dans un premier temps la méthode statique ComparisonChain.start()
  2. on teste ensuite chaque propriété par une suite d'appels à la méthode ComparaisonChain.compare
  3. enfin on récupère le résultat de la comparaison à l'aide de la méthode ComparaisonChain.result

Voici un exemple de l'utilisation de ComparisonChain dans le cas de la comparaison de deux livres :

public int compare(final Livre livre1,
                   final Livre livre2)
{
    return ComparisonChain
            .start()
            .compare(livre1.getAuteur(),
                     livre2.getAuteur())
            .compare(livre1.getAnnee(),
                     livre2.getAnnee())
            .compare(livre1.getTitre(),
                     livre2.getTitre())
            .result();
}

 

Nous voyons que la logique de comparaison est simplifiée.

Comparaison de propriétés spécifiques

De plus, la méthode compare peut prendre un troisième paramètre de type Comparator ou Ordering . Ceci permet d'indiquer un méthode de comparaison spécifique pour une propriété en indiquant un comparateur spécifique.

Ainsi, si nous souhaitons ne pas prendre en compte les accents dans la comparaison de chaînes de caractères, nous pouvons définir un comparateur de type Collator qui étend la classe Comparator.

Nous obtenons le code suivant :

public int compare(final Livre livre1,
                   final Livre livre2)
{
    // Comparator qui ne tient pas compte des accents
    final Collator collator = Collator.getInstance(Locale.FRENCH);
    collator.setStrength(Collator.PRIMARY);

    return ComparisonChain
            .start()
            .compare(livre1.getAuteur(),
                     livre2.getAuteur(),
                     Ordering.from(collator))
            .compare(livre1.getAnnee(),
                     livre2.getAnnee())
            .compare(livre1.getTitre(),
                     livre2.getTitre(),
                     Ordering.from(collator))
            .result();
}

 

Gérer les propriétés nulles pour éviter les NullPointerException

Attention : ComparisonChain ne gère pas les cas où une des instances à comparer est nulle.
Pour éviter d'avoir des exceptions NullPointerException, nous devons gérer les cas des instances nulles de chaque propriété et des instances à comparer.

Pour cela, il est important d'utiliser la méthode Ordering.nullsFirst() que ce soit à la comparaison des propriétés ou lors du tri des éléments.

Ainsi, nous utilisons la classe Ordering pour étendre les comparateurs de la manière suivante :

// Comparateur
Comparator comparatorWithoutNullPointerException = 
    Ordering.from(comparator).nullFirst();

 

Ainsi dans le code ci-dessus, le cas où deux propriétés ne sont pas définies n'est pas géré.

L'expression suivante permet de gérer les valeurs nulles qui seront situées avant les valeurs non nulles :

// Gestion des instances nulles
Ordering.natural().nullsFirst();

 

Dans le cas où pour comparer les propriétés le Ordering repose sur un comparateur existant et que nous voulons gérer les cas où une des valeurs est nulles, nous aurons l'expression suivante :

Ordering.from(comparator).nullFirst();

 

Méthode de comparaison gérant les valeurs nulles

Dans notre exemple, nous avons un comparateur de type Collator au niveau de l'auteur et du titre, et pas de comparateur spécifique au niveau de l'année. Nous obtenons le code suivant pour gérer les valeurs nulles :

public int compare(final Livre livre1,
                   final Livre livre2)
{
     // Comparer sans tenir compte des accents et de la casse
     final Collator collator = Collator.getInstance(Locale.FRENCH);
     collator.setStrength(Collator.PRIMARY);

     // Comparaison sur les propriétés des livres
     return ComparisonChain
             .start() 
             .compare(livre1.getAuteur(),
                      livre2.getAuteur(),
                      Ordering.from(collator).nullsFirst())
             .compare(livre1.getAnnee(),
                      livre2.getAnnee(),
                      Ordering.natural().nullsFirst())
             .compare(livre1.getTitre(),
                      livre2.getTitre(),
                      Ordering.from(collator).nullsFirst())
             .result();
}

 

Lancer le tri à l'aide d'un ComparisonChain

Nous pouvons effectuer le tri à l'aide d'un ComparisonChain à l'aide de l'appel à la méthode Ordering.sortedCopy. Il faut également ne pas oublier d'utiliser la méthode Ordering.nullsFirst() pour gérer les cas des instances à comparer non défini et éviter ainsi les NullPointerException :

// Comparateur de livres
LivreComparator livreComparator = 
    Ordering.from(new LivreComparator()).nullFirst();

 

Exemple complet

Voici un exemple complet de tri de livres en utilisant ComparisonChain et Ordering .

Les livres sont triés suivant l'auteur, le titre puis l'année :

public class LivreComparator
	implements Comparator<Livre> 
{	
    // Comparaison de livres
    public int compare(final Livre livre1,
                       final Livre livre2)
    {
    	 // Comparer s'en tenir compte des accents et de la casse
    	final Collator collator = Collator.getInstance(Locale.FRENCH);
        collator.setStrength(Collator.PRIMARY);
        
        // Comparaison sur les propriétés des livres
        return ComparisonChain
                .start()
                .compare(livre1.getAuteur(),
                         livre2.getAuteur(),
                         Ordering.from(collator).nullsFirst())
                .compare(livre1.getAnnee(),
                         livre2.getAnnee(),
                         Ordering.natural().nullsFirst())
                .compare(livre1.getTitre(),
                         livre2.getTitre(),
                         Ordering.from(collator).nullsFirst())
                .result();
    }    
}

 

Nous trions les livres en utilisant ce comparateur :

LivreComparator livreComparator = new LivreComparator();

// Gérer le cas des livres non définis (éviter les NullPointerException)
Ordering<Livre> livreOrdering =
    Ordering.from(livreComparator).nullsFirst();

// Trier la liste des livres
List<Livre> livresResultat = livreOrdering.sortedCopy(livres);

 

Conclusion

L'utilisation des classes Ordering et ComparisonChain de Guava permettent de faciliter et de réduire les bugs en diminuant les cas complexes de comparaison des propriétés multiples des objets. Ceci permet de réduire les erreurs et de rendre plus fiable les méthodes de tri sur les éléments en utilisant les méthodes de comparaisons existantes, en chaînant les comparaisons des propriétés des objets et en gérant les cas des instances non définies.

Cependant, l'apprentissage de l'utilisation de ces classes demandent un peu de pratique mais qui ensuite apporte ces fruits en diminuant la complexité du code à écrire.

Référence

Voici quelques références sur les classes Ordering et ComparisonChain de la bibliothèque Guava :

  • Comparaison avec la méthode compareTo : compareTo

Un commentaire

  1. matkontov.co.i

    Thanks so much for your downright blog;this is the stuff that keeps me on track through out the day. I have been searching around for your site after asked to visit them from a buddy and was thrilled when I found it after searching for awhile. Being a…

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.