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
ComparableetComparator. - 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
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éthode | Effet |
|---|---|
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 coursEn accès libre, sans inscription.