Archives Wild Code School
Java Avancé

Collection 02 : Comparable et Comparator

Quête 2 sur 15

Dans la vie, il y a deux sortes de listes : celles qui sont triées, et les autres. Aujourd'hui, tu vas découvrir comment trier une liste d'éléments en Java !

Objectifs

  • Savoir trier une liste d'éléments
  • Savoir implémenter l'interface Comparable
  • Savoir instancier un Comparator

Comparable

Tu désires reprendre et améliorer la gestion des menus d'un fastfood, vu dans la quête Collection 01 : List.

Souviens-toi, tu avais une classe Food :

java
public class Food {

    // attributes
    private String name;
    private int calories;

    public Food(String name, int calories) {
	    this.name = name;
	    this.calories = calories;
	}

    // getters and setters omitted for brevity...
}

Et tu avais la liste suivante :

java
Food burger = new Food("Big Wac", 450);
Food fries = new Food("French fries", 275);
Food cheeseburger = new Food("Cheeseburger", 300);
Food sunday = new Food("Chocolate Sunday", 175);

List<Food> lunch = new ArrayList<>();
lunch.add(burger);
lunch.add(fries);
lunch.add(cheeseburger);
lunch.add(sunday);

Tu désires maintenant trier cette liste par ordre croissant de calories : ton premier réflexe serait de développer un algorithme de tri. Cependant, il existe une méthode sort, de la classe Collections :

java
Collections.sort(lunch);

Pourtant, si tu essaies de compiler ce code, ça ne marchera pas :

shell
method Collections.<Food>sort(List<Food>) is not applicable

C'est logique : comment la méthode sort pourrait-elle savoir que tu désires trier une instance de Food sur son attribut calories ?

Pour ce faire, il faut que la classe à trier implémente l'interface Comparable<T> :

java
public interface Comparable<T> {

    int compareTo(T var1);
}

Ici T représente une classe générique : si tu n'as pas encore vu ce concept, sache qu'elle représente n'importe quelle classe objet. Pour plus d'information sur le sujet, consulte l'article correspondant en ressource.

Comme tu peux le remarquer, l'interface va requérir l'implémentation d'une méthode compareTo : c'est avec elle que tu vas jouer !

java
public class Food implements Comparable<Food> {

    @Override
    public int compareTo(Food food) {
        return 0;
    }

    // ...
}

La méthode compareTo fonctionne de la façon suivante : un objet est fourni en paramètre de la méthode et va être comparé à l'instance en cours :

  • si le retour est inférieur à zéro, l'objet sera classé après l'instance (l'objet est plus petit que l'instance)

  • si le retour est supérieur à zéro, l'objet sera classé avant l'instance (l'objet est plus grand que l'instance)

  • si le retour est égal à zéro, l'objet ne changera pas de position (l'objet est égal à l'instance)

java
public int compareTo(Food food) {
    return food.getCalories() - this.getCalories();
}

Cette façon permet de classer les éléments de manière croissante :

shell
$ Chocolate Sunday 175
$ French fries 275
$ Cheeseburger 300
$ Big Wac 450

Il te suffirait d'intervertir les éléments à comparer afin d'inverser l'ordre :

java
this.getCalories() - food.getCalories()

↗
Baeldung : The Basics of Java Generics
baeldung.com
↗
Java T Point : Java Comparable interface
javatpoint.com

Comparator

Tu as vu comment ajouter un comportement de tri par défaut à un objet. Cependant, tu peux vouloir trier ces mêmes éléments d'une manière différente : par exemple, par ordre alphabétique.

Pour "surcharger" le comportement par défaut (qui est ici "trier par calories"), ta vas pouvoir utiliser une implémentation de l'interface Comparator.

java
new Comparator<Food>() {

    @Override
    public int compare(Food food1, Food food2) {
        return 0;
    }
}

Ici la méthode compare va fonctionner de la même façon que la méthode compareTo vue dans l'étape précédente. Dans ton cas, pour classer par ordre alphabétique, il te faudra écrire le code :

java
Comparator<Food> comparator = new Comparator<Food>() {

    @Override
    public int compare(Food food1, Food food2) {
        return food1.getName().compareTo(food2.getName());
    }
};

Ici la méthode compareTo de la classe String permet de trier par ordre alphabétique

Afin de classer la liste, tu pourras appeler une surcharge de la méthode sort de Collections, de la façon suivante :

java
Collections.sort(lunch, comparator);

shell
$ Big Wac 450
$ Cheeseburger 300
$ Chocolate Sunday 175
$ French fries 275

↗
Program Creek : Comparable vs. Comparator in Java
programcreek.com
↗
Callicoder : Java Comparable and Comparator interface examples
callicoder.com

Challenge

Endgame

La population de héros réduite de moitié, Thanos a enfin terminé son travail. Aussi lui vient-il une révélation tardive : que va-t-il faire maintenant ?

Il hésite entre devenir fermier et recenser des héros restants, afin d'effectuer des statistiques.

Thanos choisit finalement la seconde option : il programme donc son gant de l'infini afin de réaliser des actions de tri sur des listes.

Pour démarrer, fais un Fork du dépôt suivant puis clone-le en local.

Pense bien à faire un Fork, sinon tu ne pourras rien pousser !

  1. Modifie la classe Hero afin d'implémenter Comparable. L'ordre de tri par défaut sera sur l'attribut name, par ordre croissant.
  2. Dans la classe Thanos, fait en sorte que la liste de héros soit bien triée comme voulu.
  3. Dans la classe Thanos, ajoute un Comparator permettant de trier la liste de héros sur l'attribut age décroissant.

Voici le résultat attendu lors de l'exécution de Thanos :

shell
$ Order by name:
$ Black Widow, 34
$ Captain America, 100
$ Hulk, 49
$ Thor, 1501
$ 
$ Order by age:
$ Thor, 1501
$ Captain America, 100
$ Hulk, 49
$ Black Widow, 34

Critères de validation

  • Le dépôt GitHub contient bien les fichiers Hero.java et Thanos.java
  • La classe Thanos se compile sans erreur et affiche dans le terminal le même résultat que celui présenté précédemment