• 6 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 30/11/2016

Généralités

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

 

Avant toute chose, je vous invite à rafraîchir votre mémoire sur les collections et leurs différentes implémentations.

Même si vous avez déjà vu des notions sur les collections dans mon cours sur Java, je vous encourage tout de même à parcourir ce chapitre où des précisions intéressantes vous attendent. :-°

Avant de commencer

Vous n'êtes pas sans savoir que les collections sont des objets qui permettent de stocker et de manipuler des objets autrement qu'avec un tableau conventionnel. Elles permettent de stocker des objets de différentes manières :

  • sous la forme d'une pile ;

  • comme une liste chaînée ;

  • sous la forme d'une structure clé-valeur ;

  • ...

Ces formes de stockage seraient impossibles à faire avec un tableau. Elles sont généralement utilisées dans ce qu’on appelle un framework : un kit de composants logiciels qui permet de structurer une application ou une fonctionnalité.

Les collections Java forment donc un framework qui permet de gérer des structures d'objets. Ce framework est constitué d'un ensemble d'interfaces dont les différentes fonctionnalités sont implémentées par des classes concrètes.

Voici un diagramme de classes représentant ces interfaces et les liens qui les unissent :

Image utilisateur

Vous voyez qu'il y aura pas mal de choses à voir... Vous pourrez aussi constater que toutes les interfaces sont génériques, il faudra donc spécifier le type d'objet qu'elles devront stocker.

Voyons maintenant les définitions de ces interfaces.

Interface globale

Ce sont les interfaces dont toutes les autres se servent afin d'avoir un comportement global commun. Ces deux interfaces représentent le plus haut niveau d'abstraction du framework.

  • Collection : c'est le plus petit dénominateur commun de toutes les interfaces. Comme vous l'aurez remarqué, toutes les interfaces (hormis les Map) implémentent cette dernière. Cette interface ne permet pas de gérer la notion de doublon ou de tri automatique. Ces notions seront employées dans les classes implémentant les interfaces spécifiques nommées ci-dessous.

  • Map : ce type de collection permet de stocker deux éléments de type générique sous la forme clé-valeur. Cela permet d’avoir un maximum de souplesse dans la gestion de vos objets.

Interfaces de premier niveau

Implémenter les interfaces de premier niveau va vous permettre de gérer les notions de doublons, de valeurs null, etc. Pour cela, vous devrez respecter certaines contraintes dans vos collections. Voici les différents types d'interfaces possibles :

  • Set : cette interface permet donc de stocker des objets. Normal, c'est une collection ! Mais les classes implémentant cette interface ne tolèrent pas les doublons de données. Ainsi, ce genre de collection est tout indiqué pour gérer des éléments uniques : un calendrier, une liste de cartes à jouer etc.

  • List : cette interface se présente sous la forme d'une liste ordonnée qui accepte les valeurs en double. Vous avez donc intérêt à contrôler parfaitement le contenu de vos données si vous utilisez ce type d'interface !

  • Queue : ce type de collections peut s'apparenter à une file d'attente. Ce sera à vous de gérer la façon d'ordonner les éléments qu'elles contiennent.

  • SortedMap : cette interface permet de stocker des éléments ordonnés par ordre croissant, très utile pour des listes de numéros de téléphone, de dictionnaire etc.

Interfaces de deuxième niveau

Ce deuxième niveau d'interfaces, présentées ci-dessous, va vous permettre de rajouter des fonctionnalités et/ou contraintes supplémentaires à vos collections : 

  • SortedSet : permet aussi d'avoir un stockage ranger par ordre croissant.

  • Deque : permet d'insérer et d'enlever des éléments aux deux bouts de la collections, un peu comme une pile de carte.

Avant de vous expliquer comment utiliser toutes ces collections, je vais commencer par vous montrer les fonctionnalités de base, comme le tri et le parcours. Cependant, si vous ne vous sentez pas à l'aise pour utiliser la généricité sur les collections, je vous invite fortement à relire la section concernée dans mon cours sur Java.

Parcourir une collection

Comme vous le savez déjà, il y a deux façons de parcourir une collection, en fonction de son type :

  • soit avec un itérateur ou un simple boucle pour les collections de type Collection ;

  • soit avec une Collection pour les collections de type Map.

Je vous propose de refaire un point rapide sur la façon de procéder. Promis, ça ne sera pas long et ça vous sera bien utile pour la suite. :)

Parcourir une implémentation de type Collection 

Comme je vous le disais, ce type d'interface comporte un objet bien pratique : un Iterator. Voici les méthodes présentes dans cette interface :

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove(); 
}

Cet objet vous permet donc de :

  • parcourir une collection, via la méthode hasNext(). Cette méthode retourne true s'il reste des éléments à parcourir ;

  • récupérer un élément, grâce à la méthode next(). Celle-ci retourne l'élément courant dans l'itérateur. Si vous invoquez cette méthode plusieurs fois dans une boucle de parcours, cela vous fait avancer dans la lecture de la collection, un peu comme si vous incrémentiez plusieurs fois le compteur d'une boucle for ;

  • supprimer un élément, en utilisant la méthode remove(). Cette méthode n'est utilisable qu'après avoir utilisé la méthode next() et n'est utilisable qu'une seule fois par appel à la méthode next().

Je vous propose un code d'exemple pour vous montrer tout ça :

import java.util.ArrayList;
import java.util.Iterator;


public class Main {

   public static void main(String[] args) {
     //nous créons une collection basique
     ArrayList<String> list = new ArrayList<String>();
     list.add("1");
     list.add("2");
     list.add("3");
     list.add("4");
     list.add("5");
     list.add("6");
     list.add("7");

     //Un petit compteur pour récupérer les tours de boucle
     int nbTourDeBoucle = 0;
     int nbTourDeBoucle2 = 0;
     
     //Nous récupérons notre itérateur
     Iterator it = list.iterator();
     
     //tant qu'il y a des éléments à parcourir
     while(it.hasNext()){
        
        nbTourDeBoucle++;
        //nous récupérons l’élément courant
        String str = (String)it.next();
        //si nous sommes sur l'élément 4, nous le retirons de la collection
        if(str.equals("4"))
           it.remove();
     }
     
     //nous reparcourons un nouvel itérateur 
     //pour nous assurer que tout a fonctionné
     it = list.iterator();
     
     while(it.hasNext()){
        
        nbTourDeBoucle2++;
        System.out.println(it.next());
        System.out.println(it.next());
        System.out.println(it.next());
     }     
     
     System.out.println("Nombre de tours de boucle N°1 : " + nbTourDeBoucle);
     System.out.println("Nombre de tours de boucle N°2 : " + nbTourDeBoucle2);
     
   }
}

Et voici le résultat :

1
2
3
5
6
7
Nombre de tours de boucle N°1 : 7
Nombre de tours de boucle N°2 : 2

Vous pouvez constater que l'élément "4" a disparu et que le nombre de tours de boucle n'est pas le même entre la première fois et la deuxième, comme je vous l'avais expliqué.

Les trois méthodes de l'interface Iterator que nous avons vues ci-dessus travaillent de concert et gèrent toutes les opérations entre elles. Lorsque vous supprimez un élément pendant le parcours, le contenu de l'itérateur ainsi que la méthode hasNext() sont mis à jour automatiquement. La méthode hasNext() sera donc automatiquement au courant qu'un élément n'est plus présent... J'ajouterai que la méthode remove() est la seule façon sûre de supprimer un élément d'une collection pendant un parcours.

Je vous disais aussi que vous pouvez parcourir ce genre de collection grâce à une boucle traditionnelle, voici comment procéder :

for(String element : list)
     System.out.println(element);
  
  //Ou encore
  for(int i = 0; i < list.size(); i++)
     System.out.println(list.get(i));

Vous avez donc deux méthodes différentes (les itérateurs et les boucles) pour parcourir ce genre de collections, mais préférez tout de même l'itérateur dans les cas suivants :

  • quand vous devez supprimer des éléments de la collection ;

  • quand vous devez parcourir plusieurs collections en parallèle.

Parcourir une collection de type Map 

En fait, les objets implémentant cette interface sont un peu particuliers de par leur nature à gérer des couples d’éléments. Du coup, les concepteurs du langage vous proposent trois façons de parcourir une telle collection, en implémentant des méthodes de l’interface Collection :

  • la méthode keySet() qui retourne une collection de type Set<K> lorsque "K" est le type de la clé de notre Map<K, V>.

  • la méthode entrySet() qui retourne elle aussi une implémentation de l'interface Set, mais de type différent. La collection retournée sera définie ainsi Set<Entry<K, V>>. Ce sera donc une collection d'objets qui contiendra tous les couples clé - valeur de notre Map. Cet objet est en fait une classe interne à l'interface Map et contient quelques méthode utiles pour récupérer des informations.

  • et enfin la méthode values() qui retourne ce type d'objet Collection<String>, c'est-à-dire la liste de nos valeurs.

Voici un code d'exemple afin de vous familiariser avec ces trois façons de faire :

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;


public class Main2 {

   /**
    * @param args
    */
   public static void main(String[] args) {
      Map<Integer, String> map = new HashMap<Integer, String>();
      map.put(1, "toto");
      map.put(2, "titi");
      map.put(3, "tutu");
      map.put(4, "tete");
      map.put(5, "tata");
      
      //Nous récupérons un Set contenant des entiers
      Set<Integer> setInt = map.keySet();
      //Utilisation d'un itérateur générique
      Iterator<Integer> it = setInt.iterator();
      System.out.println("Parcours d'une Map avec keySet : ");
      //ensuite vous savez faire
      while(it.hasNext()){
         int key = it.next();
         System.out.println("Valeur pour la clé " + key + " = " + map.get(key));
      }
      System.out.println("-----------------------------------");
      
      
      Set<Entry<Integer, String>> setEntry = map.entrySet();
      //Utilisation d'un iterateur générique
      Iterator<Map.Entry<Integer, String>> itEntry = setEntry.iterator();
      System.out.println("Parcours d'une Map avec setEntry : ");
      //ensuite vous savez faire
      while(itEntry.hasNext()){
         Map.Entry<Integer, String> entry = itEntry.next();
         System.out.println("Valeur pour la clé " + entry.getKey() + " = " + entry.getValue());
      }
      System.out.println("-----------------------------------");
      
      
      Collection<String> col = map.values();
      Iterator<String> itString = col.iterator();
      System.out.println("Parcours de la liste des valeurs d'une Map avec values : ");
      //ensuite vous savez faire
      while(itString.hasNext()){
         String value = itString.next();
         System.out.println("Valeur : " + value);
      }
      System.out.println("-----------------------------------");
      
   }
}

Et voici le résultat :

Parcours d'une Map avec keySet : 
Valeur pour la clé 1 = toto
Valeur pour la clé 2 = titi
Valeur pour la clé 3 = tutu
Valeur pour la clé 4 = tete
Valeur pour la clé 5 = tata
-----------------------------------
Parcours d'une Map avec setEntry : 
Valeur pour la clé 1 = toto
Valeur pour la clé 2 = titi
Valeur pour la clé 3 = tutu
Valeur pour la clé 4 = tete
Valeur pour la clé 5 = tata
-----------------------------------
Parcours de la liste des valeurs d'une Map avec values : 
Valeur : toto
Valeur : titi
Valeur : tutu
Valeur : tete
Valeur : tata
-----------------------------------

Du coup, quand doit-on utiliser telle ou telle méthode ?

En fait, tout dépend de ce que vous allez faire et de quelles données vous devrez manipuler.

  • Si vous avez uniquement besoin des valeurs, préférez de loin la méthode values().

  • Si vous n'avez que les clés à manipuler, utilisez la méthode keySet().

  • Si vous avez besoin à la fois des clés et des valeurs de vos données, utilisez entrySet(). Même si vous avez la possibilité de récupérer les valeurs d'une Map en fonction d'une clé, utilisez tout de même cette méthode car vous solliciterez moins la JVM.

Voilà, vous savez maintenant parcourir des collections. :)

Trier vos collections

Alors, dans cette section, nous allons aborder une nouveauté. Déjà, vous devez savoir que certaines implémentations de l'interface Collection savent naturellement trier leur contenu, c'est le cas des objets TreeSet. Voyez plutôt :

Set<String> tree = new TreeSet();
tree.add("André");
tree.add("Gislain"); 
tree.add("Matthieu"); 
tree.add("Cyrille");
tree.add("Zoé");
tree.add("Thierry");

Iterator<String> it = tree.iterator();
while(it.hasNext())
   System.out.println(it.next());

Ce qui nous donne :

André
Cyrille
Gislain
Matthieu
Thierry
Zoé

Mais vous avez aussi la possibilité d'utiliser la méthode sort() de l'objet Collections, ainsi :

List<Double> list = new ArrayList<Double>();
list.add(-0.25d);
list.add(12.52d);
list.add(56.25d);
list.add(-45.12d);
list.add(-100.11d);
list.add(0.005d);
Collections.sort(list);
Iterator<Double> it = list.iterator();
while(it.hasNext())
   System.out.println(it.next());

Ce qui nous donne :

-100.11
-45.12
-0.25
0.0050
12.52
56.25

Mais comment Java sait-il trier tout ceci ?

J'allais y venir... En fait, il existe un mécanisme dans Java qui permet de savoir si un objet est plus petit qu'un autre et la plupart des objets représentant les types de bases possèdent ce mécanisme. Ce dernier existe grâce à l'interface Comparable<T> qui n'offre qu'une méthode, compareTo(T object), qui retourne un entier et, en fonction de ce dernier, permet de savoir si le paramètre est plus petit, égal ou plus grand que l'objet interrogeant. La règle est la suivante : si compareTo() retourne un entier négatif, le paramètre est plus petit, si elle retourne 0, le paramètre est égal et le paramètre sera considéré comme plus grand si la méthode retourne un entier positif : c'est tout simple !

Voici un tableau récapitulatif des objets implémentant cette interface et de leur mode de tri :

Objet

Tri

Byte, Long, Integer, Short, Double, Float, BigInteger, BigDecimal

Tri du plus petit au plus grand.

Character

Tri du plus petit au plus grand en se servant de la représentation numérique du caractère.

Boolean

Tri du plus petit au plus grand, avec false plus petit que true.

String

Tri par ordre alphabétique.

File

Dépend des chemins d'accès aux fichiers et donc du système d'exploitation, mais se base sur l'ordre alphabétique des chemins.

Date

Tri chronologique.

CollationKey

Spécifique aux paramètres lexicographiques.

Vous pouvez donc trier des collections en utilisant ces objets selon leurs règles de tri prédéfinies dans le tableau ci-dessus… mais vous pouvez également définir vos propres règles de tri en utilisant des objets perso qui implémentent l'interface Comparable.

Attends ! Tu veux dire que nous ne pouvons pas trier des chaînes de caractères par leur taille par exemple ?

Si, bien sûr, mais avant de vous expliquer comment définir une façon de comparer spécifique, nous allons nous attarder à créer des objets et implémenter l'interface Comparable.

Voici la classe que nous allons utiliser pour comprendre tout ceci :

public class CD {
   private String auteur, titre;
   private double prix;
   public CD(String auteur, String titre, double prix) {
      this.auteur = auteur;
      this.titre = titre;
      this.prix = prix;
   }
   
   public String toString() {
      return "CD [auteur=" + auteur + ", titre=" + titre + ", prix=" + prix
            + "]";
   }

   public String getAuteur() {
      return auteur;
   }

   public String getTitre() {
      return titre;
   }

   public double getPrix() {
      return prix;
   }
}

C'est une classe toute simple et nous allons voir si nous arrivons à la trier :

import java.util.ArrayList;
public class Main4 {

   public static void main(String[] args) {
      List<CD> list = new ArrayList<CD>();
      list.add(new CD("Les arcandiers", "7€", 7d));
      list.add(new CD("Frank Zappa", "Tinseltown rebellion", 10.25d));
      list.add(new CD("Frank Zappa", "Bongo Fury", 10.25d));
      list.add(new CD("King Crimson", "red", 15.30d));
      list.add(new CD("Joe Zawinul", "World tour", 12.15d));
      
      
      System.out.println("Avant le tri : ");
      Iterator<CD> it = list.iterator();
      while(it.hasNext())
         System.out.println(it.next());
      
      Collections.sort(list);
      
      System.out.println("Après le tri : ");
      it = list.iterator();
      while(it.hasNext())
         System.out.println(it.next());
         
   }
}

Sans exécuter ce code, vous pouvez voir qu'Eclipse n'aime pas du tout la ligne suivante : Collections.sort(list);
Voici ce qu'il nous dit :

Image utilisateur

Nous allons donc maintenant implémenter notre interface dans notre classe CD. Voici le code complet de cette classe :

public class CD implements Comparable{
   private String auteur, titre;
   private double prix;
   public CD(String auteur, String titre, double prix) {
      this.auteur = auteur;
      this.titre = titre;
      this.prix = prix;
   }
   
   public String toString() {
      return "CD [auteur=" + auteur + ", titre=" + titre + ", prix=" + prix
            + "]";
   }

   @Override
   public int compareTo(Object o) {
      if(o.getClass().equals(CD.class)){
         //Nous allons trier sur le nom d'artiste
         CD cd = (CD)o;
         return this.auteur.compareTo(cd.getAuteur());
      }
      return -1;
   }

   public String getAuteur() {
      return auteur;
   }

   public String getTitre() {
      return titre;
   }

   public double getPrix() {
      return prix;
   }
}

Et maintenant notre programme peut être lancé sans problème :

Avant le tri : 
CD [auteur=Les arcandiers, titre=7€, prix=7.0]
CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
CD [auteur=King Crimson, titre=red, prix=15.3]
CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
Après le tri : 
CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
CD [auteur=King Crimson, titre=red, prix=15.3]
CD [auteur=Les arcandiers, titre=7€, prix=7.0]

Et voilà ! Le tour est joué !
Ici, nous n'avons défini qu'une façon très simple de comparer nos objets. Vous aurez remarqué que, pour un même auteur, nous pouvons avoir plusieurs albums... Pour arranger cela, nous n’avons qu'à gérer ce cas dans notre méthode compareTo(), comme ceci :

public int compareTo(Object o) {
   if(o.getClass().equals(CD.class)){
      //Nous allons trier sur le nom d'artiste
      CD cd = (CD)o;
      //Si les deux CD ont le même auteur, on trie sur le nom de l'album
      if(this.auteur.compareTo(cd.getAuteur()) == 0)
           return this.titre.compareTo(cd.getTitre());
      return this.auteur.compareTo(cd.getAuteur());
   }
   return -1;
}

Voyons maintenant comment la méthode Collections.sort() fonctionne. Déjà, vous pourrez constater que la signature de cette méthode est très... étrange... La voici pour ceux qui ne l’auraient pas remarqué :

Image utilisateur

La méthode attend donc un certain type d'objet et se sert des résultats retournés par la méthode compareTo() pour ranger les éléments.

Attends ! Tu nous dis que nous ne pourrons trier que des List ?

De cette manière, oui. Mais vous devez savoir que certaines implémentations de l'interface Set sont déjà triées, toujours parce que les objets qu’elles contiennent implémentent l'interface Comparable<T>.
Par contre, pour pouvoir trier des collections de type Map nous allons utiliser une autre interface qui permet, en plus, de trier de façon différente des objets implémentant déjà l'interface Comparable<T>.

Une nouvelle façon de trier : l'interface Comparator<T>

Précédemment, nous avons vu comment rendre un objet comparable à un autre afin de pouvoir trier nos collections, de type List. Mais maintenant, si nous souhaitons de temps en temps trier nos CD non plus en fonction de leur auteur et leur nom mais en fonction de leur prix, c'est là que l'interface Comparator<T> entre en jeu !

En fait, il existe une autre méthode sort() dans la classe Collections qui, elle, prend une collection et un objet de type Comparator<T> en paramètre.

Que contient cette interface ?

Elle contient une méthode equals(Object o) et une méthode compare(T el1, T el2). C'est cette dernière qui va nous intéresser ici. Elle fonctionne de la même manière que sa petite cousine compareTo() dans le sens où elle renvoie un entier négatif, positif ou égal à 0 dans les différents cas, comme vu précédemment.

Ce que je vous propose, c'est de voir comment trier notre liste de CD par prix, tout en conservant la méthode compareTo() dans la classe CD. Voici le code qui permet de faire cela :

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;


public class Main5 {

   public static void main(String[] args) {
      List<CD> list = new ArrayList<CD>();
      list.add(new CD("Les arcandiers", "7€", 7d));
      list.add(new CD("Frank Zappa", "Tinseltown rebellion", 10.25d));
      list.add(new CD("Frank Zappa", "Bongo Fury", 10.25d));
      list.add(new CD("King Crimson", "red", 15.30d));
      list.add(new CD("Joe Zawinul", "World tour", 12.15d));
      
      
      System.out.println("Avant le tri : ");
      Iterator<CD> it = list.iterator();
      while(it.hasNext())
         System.out.println(it.next());
      
      Collections.sort(list);
      
      System.out.println("Après le tri : ");
      it = list.iterator();
      while(it.hasNext())
         System.out.println(it.next());
      
      System.out.println("Après le tri avec notre comparateur");
      //nous créons une classe anonyme ici, mais rien ne vous empêche d'en créer une dans un fichier séparé
      Collections.sort(list, new Comparator<CD>(){
         public int compare(CD cd1, CD cd2) {
            Double prix1 = (Double)cd1.getPrix();
            Double prix2 = (Double)cd2.getPrix();
            int result = prix1.compareTo(prix2);
            //dans le cas ou 2 CD auraient le même prix...
            if(result == 0){
               return cd1.compareTo(cd2);
            }
            return result;
         }
      });

      it = list.iterator();
      while(it.hasNext())
         System.out.println(it.next());
   }
}

Et le résultat nous donne :

Avant le tri : 
CD [auteur=Les arcandiers, titre=7€, prix=7.0]
CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
CD [auteur=King Crimson, titre=red, prix=15.3]
CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
Après le tri : 
CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
CD [auteur=King Crimson, titre=red, prix=15.3]
CD [auteur=Les arcandiers, titre=7€, prix=7.0]
Après le tri avec notre comparateur
CD [auteur=Les arcandiers, titre=7€, prix=7.0]
CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
CD [auteur=King Crimson, titre=red, prix=15.3]

Vous pouvez voir que ça fonctionne très bien !

Bon c'est simple, OK. Mais comment trier une implémentation de Map ?

Déjà, si vous voulez avoir une implémentation de Set triée, je vous invite à utiliser l’objet TreeSet ou SortedSet. Pour pouvoir trier les objets que vous insérerez dans ce genre de collections, il faudra que ces objets implémentent l'interface Comparable, ou bien que vous passiez par un comparateur dans le constructeur. Voici un code d'exemple :

import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;


public class Main6 {
   public static void main(String[] args){
      Set<CD> set1 = new TreeSet<CD>();
      set1.add(new CD("Les arcandiers", "7€", 7d));
      set1.add(new CD("Frank Zappa", "Tinseltown rebellion", 10.25d));
      set1.add(new CD("Frank Zappa", "Bongo Fury", 10.25d));
      set1.add(new CD("King Crimson", "red", 15.30d));
      set1.add(new CD("Joe Zawinul", "World tour", 12.15d));
      
      Iterator<CD> it = set1.iterator();
      while(it.hasNext())
           System.out.println(it.next());
      
      //On crée directement un nouvel objet
      //en lui spécifiant sa façon de ranger les objets
      Set<CD> set2 = new TreeSet<CD>(new Comparator<CD>(){
         public int compare(CD cd1, CD cd2) {
            Double prix1 = (Double)cd1.getPrix();
            Double prix2 = (Double)cd2.getPrix();
            int result = prix1.compareTo(prix2);
            //dans le cas ou 2 CD auraient le même prix...
            if(result == 0){
               return cd1.compareTo(cd2);
            }
            return result;
         }
      });
      
      //On ajoute le contenu de la première collection
      //dans la deuxième
      set2.addAll(set1);
      System.out.println("-------------------------------");
      it = set2.iterator();
      while(it.hasNext())
           System.out.println(it.next());
   }
}

Ce qui nous donne :

CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
CD [auteur=King Crimson, titre=red, prix=15.3]
CD [auteur=Les arcandiers, titre=7€, prix=7.0]
-------------------------------
CD [auteur=Les arcandiers, titre=7€, prix=7.0]
CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
CD [auteur=King Crimson, titre=red, prix=15.3]

Nous avons donc bien des implémentations de Set qui se comportent différemment selon les cas : challenge réussi !

Maintenant, voyons comment trier une Map... Dans ce cas, il nous faudra une classe concrète car nous allons avoir besoin de stocker le contenu à trier. Nous allons ensuite créer une nouvelle classe de type TreeMap qui aura notre comparateur en paramètre dans son constructeur, il ne nous restera plus qu'à ajouter le contenu de la Map que nous souhaitons trier dans celle qui contiendra le contenu trié. Afin de vous montrer ce fonctionnement, je vous propose de trier une Map<Integer, CD> d'abord sur le CD et ensuite sur l'indice de façon décroissante. Voici mes codes :

import java.util.Comparator;
import java.util.Map;
public class MapComparator implements Comparator<Integer> {

   Map<Integer, CD> map;
   public MapComparator(Map<Integer, CD> map){
      this.map = map;
   }
   
   @Override
   public int compare(Integer key1, Integer key2) {
      CD cd1 = map.get(key1);
      CD cd2 = map.get(key2);
      return cd1.compareTo(cd2);
   }
}
import java.util.Comparator;
import java.util.Map;
public class MapComparatorDesc implements Comparator<Integer> {

   Map<Integer, CD> map;
   public MapComparatorDesc(Map<Integer, CD> map){
      this.map = map;
   }
   
   @Override
   public int compare(Integer key1, Integer key2) {
      //En comparant la clé dans ce sens
      //les éléments seront rangés par ordre décroissant d'indice
      return key2.compareTo(key1);
   }
}
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Main7 {

   public static void main(String[] args) {
      Map<Integer, CD> map1 = new HashMap<Integer, CD>();
      map1.put(1, new CD("Les arcandiers", "7€", 7d));
      map1.put(3, new CD("Frank Zappa", "Tinseltown rebellion", 10.25d));
      map1.put(5, new CD("Frank Zappa", "Bongo Fury", 10.25d));
      map1.put(4, new CD("King Crimson", "red", 15.30d));
      map1.put(2, new CD("Joe Zawinul", "World tour", 12.15d));
      
      //dans ce cas de figure, notre Map est triée par indice croissant
      Set<Map.Entry<Integer, CD>> es1 = map1.entrySet();
      Iterator<Map.Entry<Integer, CD>> it = es1.iterator();
      while(it.hasNext()){
         Map.Entry entry = it.next();
         System.out.println("clé : " + entry.getKey() + " - valeur : " + entry.getValue());
      }
      
      //Nous allons maintenant trier par ordre de CD
      System.out.println("-----------------------------------");
      
      //nous passons notre comparateur dans le constructeur
      //en lui spécifiant le contenu de la collection que nous allons lui passer
      Map<Integer, CD> map2 = new TreeMap<Integer, CD>(
                                   new MapComparator(map1)
                              );
      
      map2.putAll(map1);      
      Set<Map.Entry<Integer, CD>> es2 = map2.entrySet();
      Iterator<Map.Entry<Integer, CD>> it2 = es2.iterator();
      while(it2.hasNext()){
         Map.Entry entry = it2.next();
         System.out.println("clé : " + entry.getKey() + " - valeur : " + entry.getValue());
      }
      
      System.out.println("-----------------------------------");
      
      //Idem ici, en changeant juste de comparateur
      Map<Integer, CD> map3 = new TreeMap<Integer, CD>(
                                   new MapComparatorDesc(map1)
                              );
      map3.putAll(map1);      
      Set<Map.Entry<Integer, CD>> es3 = map3.entrySet();
      Iterator<Map.Entry<Integer, CD>> it3 = es3.iterator();
      while(it3.hasNext()){
         Map.Entry entry = it3.next();
         System.out.println("clé : " + entry.getKey() + " - valeur : " + entry.getValue());
      }      
   }
}

Et voici le résultat, sous vos yeux ébahis :

clé : 1 - valeur : CD [auteur=Les arcandiers, titre=7€, prix=7.0]
clé : 2 - valeur : CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
clé : 3 - valeur : CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
clé : 4 - valeur : CD [auteur=King Crimson, titre=red, prix=15.3]
clé : 5 - valeur : CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
-----------------------------------
clé : 5 - valeur : CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
clé : 3 - valeur : CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
clé : 2 - valeur : CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
clé : 4 - valeur : CD [auteur=King Crimson, titre=red, prix=15.3]
clé : 1 - valeur : CD [auteur=Les arcandiers, titre=7€, prix=7.0]
-----------------------------------
clé : 5 - valeur : CD [auteur=Frank Zappa, titre=Bongo Fury, prix=10.25]
clé : 4 - valeur : CD [auteur=King Crimson, titre=red, prix=15.3]
clé : 3 - valeur : CD [auteur=Frank Zappa, titre=Tinseltown rebellion, prix=10.25]
clé : 2 - valeur : CD [auteur=Joe Zawinul, titre=World tour, prix=12.15]
clé : 1 - valeur : CD [auteur=Les arcandiers, titre=7€, prix=7.0]

Et voilà, le tri n'a plus de secrets pour vous. :)
Ce n'était pas très difficile mais encore fallait-il le savoir.

Quand vaut-il mieux utiliser un comparateur plutôt que l'interface Comparable ?

Dites-vous bien que l'implémentation de l'interface Comparable permet de donner à vos objets une façon de les comparer par défaut, c'est donc par le biais de cette interface qu'il faut définir un tel comportement. Par contre, avec l'interface Comparator vous pouvez définir des méthodes de comparaison marginale afin de remplacer ponctuellement l'ordre préétabli. C'est donc à vous de juger quel comportement est prédominant par rapport à un autre et donc mettre le comportement le plus fréquent dans compareTo(T elem).

Les méthodes usuelles

Avant de clore ce chapitre je vous propose de faire un rapide tour d'horizon sur les méthodes qu’offrent les interfaces mères. Ce sera rapide et sans douleur, promis. :)

Les méthodes de l'interface Collection

Méthode

Description

clear()

Supprime tous les éléments de la collection.

addAll(Collection<? extends E> c)

permet d'ajouter tout le contenu d'une autre collection dans une nouvelle instance. Retourne true si l'action a abouti.

containsAll(Collection<?> c)

Retourne true si la collection contient tous les éléments de la collection passée en paramètre.

isEmpty()

Retourne true si la collection ne contient aucun élément.

removeAll(Collection<?> c)

Supprime tous les éléments de la collection se trouvant dans la collection passée en paramètre.

retainAll(Collection<?> c)

Ne conserve dans la collection que les éléments présents dans la collection passée en paramètre.

Les méthodes de l'interface Map

Méthode

Description

clear()

Supprime tous les éléments de la collection.

containsKey(Object key)

Retourne true si la key passée en paramètre est présente dans la Map.

containsValue(Object value)

Idem que la méthode ci-dessus mais pour la valeur cette fois.

isEmpty()

Retourne true si la Map est vide.

putAll(Map<? extends K,? extends V> m)

Remplit la collection avec le contenu de l'objet passé en paramètre. Retourne true s'il a réussi.

Il existe aussi une classe qui se nomme Collections, qui contient un bon nombre de méthode usuelles mais qui ne fonctionnent que sur certaines interfaces. Je vous les présenterai donc au moment opportun. :)

Avant de clore ce chapitre, je tiens à ajouter une dernière chose. Je ne sais pas si vous avez remarqué mais, depuis le début de ce chapitre, j'instancie mes collections d'une certaine façon :

//La syntaxe en diamant est utilisable depuis Java 7
//Mais ce n'est pas là mon propos ;)
List<Integer> list = new ArrayList<>();
Set<String> set = new TreeSet<>();
Map<Double, Double> map = new HashMap<>();

J'utilise l'interface comme type d'objet, afin de vous faciliter la tâche au cas où vous souhaiteriez changer d'implémentation... En procédant de la sorte, si vous ne souhaitez plus utiliser une ArrayList mais une LinkedList, vous n'avez qu'un mot à changer.

C'est une bonne règle de conduite et je vous invite à l'utiliser.

Nous avons vu pas mal de notions importantes ici et vous devriez mieux contrôler vos collections maintenant que vous savez les parcourir, les trier etc.
Je vous propose maintenant de passer en revue les différentes implémentations en commençant par les List.

Exemple de certificat de réussite
Exemple de certificat de réussite