Skip to content

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

String[] noms = {"Alice", "Bob", "Charlie"};
→ Taille fixe, peu de méthodes utiles.

Exemple avec collection

List<String> noms = new ArrayList<>();
noms.add("Alice");
noms.add("Bob");
noms.add("Charlie");
→ 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 aussi Deque).
  • 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.

Set<String> uniques = new HashSet<>(List.of("a", "b", "a"));
System.out.println(uniques); // [a, b]

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.

enum Droit { LIRE, ECRIRE, EXECUTER }
EnumSet<Droit> droits = EnumSet.of(Droit.LIRE, Droit.ECRIRE);

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.

EnumMap<Droit, Boolean> map = new EnumMap<>(Droit.class);
map.put(Droit.ECRIRE, true);

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.
List<Integer> immu = List.of(1,2,3);
// immu.add(4); // UnsupportedOperationException

Concurrence

  • CopyOnWriteArrayList pour lecture majoritaire.
  • ConcurrentHashMap pour accès concurrent.
  • LinkedBlockingQueue pour 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 hashCode si equals redéfini.
  • Modifier une collection pendant for-each.
  • Utiliser Vector ou Stack → préférez ArrayList / ArrayDeque.
  • Mettre null dans List.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.