Les Collections en Java¶
Pourquoi utiliser les collections ?¶
Une collection est un objet qui regroupe plusieurs éléments dans une même structure.
Les collections permettent de stocker, manipuler et parcourir des ensembles de données sans se limiter à la taille fixe des tableaux (arrays).
Exemple sans collection¶
→ Taille fixe, peu de méthodes utiles.Exemple avec collection¶
→ Taille dynamique, nombreuses méthodes (ajouter, supprimer, trier, rechercher, etc.).L’interface Collection<E>¶
L’interface Collection<E> définit les comportements communs de la plupart des collections.
E représente le type des éléments contenus (par exemple String, Integer, etc.).
Méthodes principales¶
add(E e) // Ajoute un élément
remove(Object o) // Supprime un élément
contains(Object o) // Vérifie la présence d’un élément
size() // Renvoie le nombre d’éléments
isEmpty() // Vérifie si la collection est vide
clear() // Supprime tous les éléments
iterator() // Retourne un itérateur pour parcourir la collection
Grandes familles de collections¶
| Interface | Description | Exemples |
|---|---|---|
| List | Ordonnée, autorise les doublons | ArrayList, LinkedList |
| Set | Aucun doublon, ordre non garanti ou trié | HashSet, TreeSet, LinkedHashSet |
| Queue / Deque | File (FIFO) ou pile (LIFO) | ArrayDeque, PriorityQueue |
| Map | Association clé → valeur (pas une sous-interface de Collection) | HashMap, TreeMap, LinkedHashMap |
Avantages par rapport aux tableaux¶
Tableaux (Array) |
Collections (List, Set, etc.) |
|---|---|
| Taille fixe | Taille dynamique |
| Peu de fonctionnalités | Méthodes riches et puissantes |
| Gestion manuelle | Automatisée et optimisée |
| Pas de tri ou unicité intégrée | Certaines collections gèrent cela automatiquement |
Interfaces et implémentations¶
Chaque interface définit un comportement général, et chaque classe l’implémente différemment.
Ainsi, vous pouvez changer l’implémentation sans modifier le reste du code.
List<String> noms = new ArrayList<>();
noms = new LinkedList<>(); // même interface, comportement différent
→ C’est le principe de programmation orientée interface.
Fonctionnement interne¶
Les collections utilisent des structures de données internes adaptées à leur rôle :
ArrayList→ tableau redimensionnable automatiquement.LinkedList→ chaîne d’éléments liés entre eux.HashSet/HashMap→ table de hachage pour des recherches rapides.
Elles sont parcourues grâce à un itérateur.
Exemple¶
Collection<String> noms = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
for (String nom : noms) {
System.out.println(nom);
}
Génériques : des collections typées¶
Les collections utilisent les génériques (<E>) pour éviter les erreurs de type à l’exécution.
List<String> noms = new ArrayList<>();
noms.add("Alice");
// noms.add(123); // Erreur de compilation
Résumé¶
| Élément | Rôle |
|---|---|
| Collection | Regroupe plusieurs objets |
| Interface | Définit le comportement général |
| Implémentation | Fournit la logique concrète (ex. ArrayList) |
| Génériques | Sécurisent les types |
| Itérateur | Permet de parcourir les éléments |
Les collections sont donc un socle fondamental en Java pour manipuler des ensembles de données de manière efficace, souple et sécurisée.
Panorama rapide¶
- List (ordre, doublons permis)
ArrayList,LinkedList,Vector(héritage),CopyOnWriteArrayList(concurrence). - Set (unicité, pas de doublons)
HashSet,LinkedHashSet,TreeSet,EnumSet. - Map (associations clé → valeur)
HashMap,LinkedHashMap,TreeMap,EnumMap,ConcurrentHashMap,WeakHashMap,IdentityHashMap. - Queue / Deque (files, piles, double extrémité)
ArrayDeque,PriorityQueue,LinkedList(implémente aussiDeque). - Collections immuables
List.of(...),Set.of(...),Map.of(...),Map.ofEntries(...).
List : séquences ordonnées (doublons autorisés)¶
ArrayList¶
Accès par index rapide, itérations fréquentes, insertions/suppressions surtout en fin.
Complexité typique : get(i) O(1) amorti ; add(e) en fin O(1) amorti ; insertions au milieu O(n).
List<String> noms = new ArrayList<>();
noms.add("Alice");
noms.add("Bob");
System.out.println(noms.get(1)); // Bob
LinkedList¶
Insertions/suppressions fréquentes au début/au milieu, usage en Deque.
Moins bon pour accès aléatoire (O(n)).
Deque<Integer> dq = new LinkedList<>();
dq.addFirst(10);
dq.addLast(20);
System.out.println(dq.removeFirst()); // 10
Itération & modification¶
List<Integer> L = new ArrayList<>(List.of(1, 2, 3, 4));
Iterator<Integer> it = L.iterator();
while (it.hasNext()) {
if (it.next() % 2 == 0) it.remove();
}
System.out.println(L); // [1, 3]
Set : unicité des éléments¶
HashSet¶
Vérifier l’appartenance, dédupliquer rapidement.
Clé : éléments doivent respecter equals/hashCode.
LinkedHashSet¶
Comme HashSet mais préserve l’ordre d’insertion.
List<String> in = List.of("b", "a", "b", "c");
Set<String> ord = new LinkedHashSet<>(in);
System.out.println(ord); // [b, a, c]
TreeSet¶
Ensemble trié (ordre naturel ou Comparator). O(log n).
Set<String> tri = new TreeSet<>(Comparator.comparingInt(String::length).thenComparing(String::compareTo));
tri.addAll(List.of("bb", "a", "ccc"));
System.out.println(tri); // [a, bb, ccc]
EnumSet¶
Ensemble d’énumérations → ultra compact et rapide.
Map : associations clé → valeur¶
HashMap¶
Table de hachage générale.
Map<String, Integer> freq = new HashMap<>();
for (var s : List.of("a","b","a")) {
freq.merge(s, 1, Integer::sum);
}
System.out.println(freq); // {a=2, b=1}
LinkedHashMap¶
Comme HashMap mais avec ordre d’insertion ou d’accès (LRU léger).
Map<Integer, String> lru = new LinkedHashMap<>(16, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry<Integer, String> e) {
return size() > 3;
}
};
lru.put(1,"A"); lru.put(2,"B"); lru.put(3,"C"); lru.get(1); lru.put(4,"D");
System.out.println(lru.keySet()); // [3, 1, 4]
TreeMap¶
Dictionnaire trié par clé.
Map<String, Integer> notes = new TreeMap<>();
notes.put("Alice", 85);
notes.put("Bob", 90);
System.out.println(notes.firstEntry()); // Alice=85
EnumMap¶
Clés d’un type enum.
Queue / Deque / Priority¶
ArrayDeque¶
Pile ou file efficace.
Deque<String> pile = new ArrayDeque<>();
pile.push("A"); pile.push("B");
System.out.println(pile.pop()); // B
PriorityQueue¶
Toujours récupérer l’élément min ou max (avec Comparator inverse).
Queue<Integer> pq = new PriorityQueue<>();
pq.addAll(List.of(5,1,4));
System.out.println(pq.poll()); // 1
Génériques, equals/hashCode, Comparator¶
Génériques¶
Préférez des types paramétrés : List<String> plutôt que List.
equals / hashCode¶
Implémentez les deux de façon cohérente.
class Utilisateur {
final String id;
final String nom;
Utilisateur(String id, String nom) { this.id = id; this.nom = nom; }
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Utilisateur u)) return false;
return id.equals(u.id);
}
@Override public int hashCode() { return id.hashCode(); }
}
Comparator¶
Pour trier sans modifier la classe.
List<String> l = new ArrayList<>(List.of("bbb","a","cc"));
l.sort(Comparator.comparingInt(String::length).thenComparing(String::compareTo));
Immutabilité et vues¶
- Immuables (Java 9+) :
List.of(...),Set.of(...),Map.of(...) - Copies non modifiables :
Collections.unmodifiableList(l) - Copies défensives : exposez des vues/collections immuables.
Concurrence¶
CopyOnWriteArrayListpour lecture majoritaire.ConcurrentHashMappour accès concurrent.LinkedBlockingQueuepour producteurs/consommateurs.
Choisir la bonne collection¶
| Objectif | Type | Implémentation recommandée |
|---|---|---|
| Ordre + doublons | List | ArrayList |
| Insertion fréquente | List | LinkedList |
| Unicité | Set | HashSet |
| Unicité + ordre | Set | LinkedHashSet |
| Unicité + tri | Set | TreeSet |
| Association clé-valeur | Map | HashMap |
| Clés triées | Map | TreeMap |
| File/Pile | Deque | ArrayDeque |
| Priorité | Queue | PriorityQueue |
Exemples¶
A. Compter les fréquences¶
Map<String,Integer> freq = new HashMap<>();
for (String s : List.of("java","java","python")) {
freq.merge(s, 1, Integer::sum);
}
Dédupliquer en conservant l’ordre¶
List<String> in = List.of("b","a","b","c","a");
List<String> out = new ArrayList<>(new LinkedHashSet<>(in));
Top-K éléments¶
int K = 3;
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int v : List.of(5,1,7,3,9,2)) {
if (minHeap.size() < K) minHeap.add(v);
else if (v > minHeap.peek()) { minHeap.poll(); minHeap.add(v); }
}
System.out.println(minHeap); // 3 plus grands
Groupement (comme GROUP BY)¶
record Étudiant(String groupe, String nom) {}
List<Étudiant> E = List.of(new Étudiant("A","Alice"), new Étudiant("B","Bob"), new Étudiant("A","Alex"));
Map<String, List<Étudiant>> parGroupe = E.stream().collect(
java.util.stream.Collectors.groupingBy(Étudiant::groupe));
Erreurs fréquentes¶
- Oublier
hashCodesiequalsredéfini. - Modifier une collection pendant
for-each. - Utiliser
VectorouStack→ préférezArrayList/ArrayDeque. - Mettre
nulldansList.of(...). - Accès aléatoire dans
LinkedList. - Négliger capacité initiale pour
ArrayList/HashMap.
Résumé : L’API Collections de Java offre un éventail très complet.
Apprenez à choisir la structure selon l'ordre, l'unicité, la performance et le tri.