Adloun

Collections et génériques

Cours · programmation Java (école d'ingénieur), chapitre 12 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun

Une application de gestion de bibliothèque doit retenir des livres dont le nombre change tous les jours, retrouver en un instant le livre d'une cote donnée, compter combien de fois chaque auteur est emprunté, servir les réservations dans l'ordre d'arrivée. Avec les seuls tableaux du chapitre 5, chacune de ces tâches demande d'écrire soi-même beaucoup de code délicat : agrandir un tableau plein, chercher une valeur case par case, décaler les éléments après une suppression.

La bibliothèque standard de Java fournit pour cela les collections : des classes toutes prêtes, efficaces et éprouvées, qui rangent des objets de mille façons. Elles reposent sur la généricité, qui permet d'écrire List<String>, une « liste de chaînes », et de laisser le compilateur vérifier que l'on n'y range jamais autre chose.

Objectifs du chapitre

  • Comprendre les limites des tableaux et le rôle des classes enveloppes (Integer, Double…).
  • Utiliser les listes (ArrayList), les ensembles (HashSet, TreeSet), les dictionnaires (HashMap, TreeMap) et les piles et files (ArrayDeque).
  • Parcourir et modifier une collection sans erreur, trier avec Comparable et Comparator.
  • Choisir la bonne collection selon les opérations à faire et leur coût.
  • Écrire une classe et une méthode génériques, avec un type borné.

12.1 Des tableaux aux collections

12.1.1 Les limites des tableaux

Un tableau a une taille fixée une fois pour toutes, à sa création. Pour y ajouter un élément, il faut créer un tableau plus grand et recopier l'ancien. Une ArrayList, au contraire, grandit toute seule :


import java.util.ArrayList;
import java.util.Arrays;

public class LimitesTableaux {
    public static void main(String[] args) {
        // Un tableau a une taille fixée à sa création.
        int[] t = new int[3];
        t[0] = 5; t[1] = 8; t[2] = 13;
        // Pour ajouter une 4e valeur, il faut créer un nouveau tableau et recopier.
        int[] u = Arrays.copyOf(t, 4);
        u[3] = 21;
        System.out.println("tableau : " + Arrays.toString(u));

        // Une ArrayList grandit toute seule.
        ArrayList<Integer> liste = new ArrayList<>();
        liste.add(5); liste.add(8); liste.add(13); liste.add(21);
        System.out.println("liste   : " + liste + " (taille " + liste.size() + ")");
    }
}

tableau : [5, 8, 13, 21]
liste   : [5, 8, 13, 21] (taille 4)

On remarque trois différences : une liste s'affiche directement avec println ; sa taille se lit par la méthode size() (et non par un attribut length) ; et le type de ses éléments s'écrit entre chevrons, <Integer>.

12.1.2 Les classes enveloppes et l'autoboxing

Une collection ne contient que des objets. Or int, double, boolean, char sont des types primitifs, pas des classes. À chacun d'eux correspond une classe enveloppe : Integer, Double, Boolean, Character, Long… On écrit donc ArrayList<Integer> et jamais ArrayList<int>, que le compilateur refuse :


ErreurInt.java:4: error: unexpected type
        ArrayList<int> l = new ArrayList<>();
                  ^
  required: reference
  found:    int
Définition 12.1Autoboxing et unboxing

Le passage d'une valeur primitive à l'objet enveloppe correspondant (int vers Integer) s'appelle l'autoboxing ; le passage inverse, l'unboxing. Le compilateur les insère automatiquement chaque fois que c'est nécessaire.


public class Emballage {
    public static void main(String[] args) {
        int n = 42;
        Integer boite = n;          // autoboxing : int -> Integer
        int m = boite + 1;          // unboxing : Integer -> int
        System.out.println(boite + " " + m);

        Integer a = 127, b = 127;
        Integer c = 128, d = 128;
        System.out.println("a == b : " + (a == b));
        System.out.println("c == d : " + (c == d));
        System.out.println("c.equals(d) : " + c.equals(d));

        System.out.println(Integer.parseInt("2026") + 1);
        System.out.println(Integer.MAX_VALUE + " " + Double.parseDouble("3.5"));
    }
}

42 43
a == b : true
c == d : false
c.equals(d) : true
2027
2147483647 3.5

Attention — comparer deux Integer avec ==

Avec deux objets, == compare les références (« est-ce le même objet ? »), pas les valeurs. Java garde en réserve un exemplaire unique de chaque Integer entre et : pour ces petites valeurs, a == b semble marcher. Dès , deux Integer égaux sont deux objets distincts et c == d vaut false. Les valeurs de deux objets se comparent toujours avec equals.

Les classes enveloppes rendent aussi des services : Integer.parseInt("2026") convertit une chaîne en entier, Integer.MAX_VALUE donne le plus grand int, Character.isDigit(c) teste si un caractère est un chiffre.

12.1.3 Une famille d'interfaces

Les collections de Java sont organisées autour de quelques interfaces (chapitre 10) qui décrivent ce que l'on peut faire, et de classes qui les réalisent de différentes façons.

En bleu, les interfaces ; en vert, les classes qui les réalisent (flèches en pointillés). Une Map n'est pas une Collection : elle range des couples clé–valeur.

Méthode — déclarer avec l'interface, créer avec la classe

On déclare une variable avec le type de l'interface et on crée l'objet avec une classe : List<String> noms = new ArrayList<>(); Le reste du programme ne dépend que de List : passer plus tard à une LinkedList ne demande de changer qu'une seule ligne. Les chevrons vides <> (le « losange ») laissent le compilateur déduire le type des éléments.

12.2 Les listes

12.2.1 ArrayList : un tableau qui grandit

Une liste est une suite d'éléments ordonnés et indicés à partir de , comme un tableau, où les doublons sont permis. ArrayList la range dans un tableau interne qu'elle agrandit quand il est plein.

MéthodeEffet
add(x)ajoute x à la fin
add(i, x)insère x à l'indice i (décale la suite)
get(i)l'élément d'indice i
set(i, x)remplace l'élément d'indice i
remove(i)retire l'élément d'indice i (si i est un int)
remove(x)retire la première occurrence de l'objet x
size(), isEmpty()nombre d'éléments ; liste vide ?
contains(x), indexOf(x)présence ; premier indice (ou )

import java.util.ArrayList;
import java.util.List;

public class ListesBase {
    public static void main(String[] args) {
        List<String> courses = new ArrayList<>();
        courses.add("pain");
        courses.add("lait");
        courses.add("œufs");
        courses.add(1, "beurre");               // insertion à l'indice 1
        System.out.println(courses);
        System.out.println("indice 2 : " + courses.get(2));
        courses.set(0, "baguette");             // remplacement
        System.out.println("contient lait ? " + courses.contains("lait"));
        System.out.println("position de lait : " + courses.indexOf("lait"));
        courses.remove("beurre");               // retrait par valeur
        System.out.println(courses + " (taille " + courses.size() + ")");

        List<Integer> nombres = new ArrayList<>(List.of(10, 20, 30, 2));
        nombres.remove(2);                      // retire l'élément d'INDICE 2 (30)
        System.out.println(nombres);
        nombres.remove(Integer.valueOf(2));     // retire la VALEUR 2
        System.out.println(nombres);
    }
}

[pain, beurre, lait, œufs]
indice 2 : lait
contient lait ? true
position de lait : 2
[baguette, lait, œufs] (taille 3)
[10, 20, 2]
[10, 20]

La suite du cours est sur Adloun

Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.

Lire la suite du cours

En accès libre, sans inscription.

Exercices corrigés de ce chapitre

Lire la suite du cours →