1. Introduction
Pour faire simple, une collection mutable, c’est une collection que l’on peut modifier après sa création : ajouter, supprimer et changer des éléments. Une collection immutable (immuable) est une collection que l’on ne peut plus modifier après sa création. Comme du béton une fois durci : on peut regarder et toucher, mais on ne peut plus « modeler » de nouvelles formes.
Une collection mutable, c’est un carnet et un crayon : on écrit, on efface, on ajoute de nouvelles notes. Une collection immuable, c’est une page plastifiée : plus personne ne peut rien y ajouter ni y effacer.
Exemples de collections mutables
En Java, la quasi-totalité des collections standard sont mutables par défaut. Par exemple :
- ArrayList
- LinkedList
- HashSet
- TreeSet
- HashMap
- LinkedHashMap
- et bien d’autres
Exemple : ArrayList
import java.util.*;
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.set(1, "Charlie"); // remplace Bob par Charlie
names.remove("Alice"); // supprime Alice
System.out.println(names); // [Charlie]
Ici, nous pouvons tout faire avec la collection : ajouter, supprimer, échanger des éléments. C’est pratique quand la collection se construit dynamiquement, par exemple lors de la lecture d’un fichier ou d’une saisie utilisateur.
2. Exemples de collections immuables
Introduites avec Java 9, vous les avez sans doute déjà croisées, mais il faut encore s’y habituer :
- List.of(...)
- Set.of(...)
- Map.of(...)
- List.copyOf(collection)
- Set.copyOf(collection)
- Map.copyOf(map)
Exemple : List.of
List<String> planets = List.of("Mercury", "Venus", "Earth", "Mars");
System.out.println(planets); // [Mercury, Venus, Earth, Mars]
planets.add("Jupiter"); // Lève UnsupportedOperationException !
Toute tentative de modifier la collection déclenche une exception à l’exécution.
Exemple : Collections.unmodifiableList
List<String> modifiable = new ArrayList<>(List.of("a", "b"));
List<String> unmodifiable = Collections.unmodifiableList(modifiable);
unmodifiable.add("c"); // UnsupportedOperationException!
Mais il y a un piège : si vous modifiez la collection source, le wrapper change aussi !
modifiable.add("c");
System.out.println(unmodifiable); // [a, b, c] — l’élément est apparu !
3. Principales différences entre collections mutables et immuables
| Propriété | Mutables | Immuables |
|---|---|---|
| Peut-on ajouter un élément ? | Oui | Non |
| Peut-on supprimer un élément ? | Oui | Non |
| Peut-on modifier un élément ? | Oui (par ex. set) | Non |
| Sécurité vis-à-vis des threads | Non (par défaut) | Oui (pas d’état — rien à modifier) |
| Peut-on ajouter null ? | Oui (généralement) | Non (dans les méthodes factory de Java 9+) |
| Implémentation | ArrayList, HashSet, etc. | List.of, Set.of, Map.of, copyOf |
4. Pourquoi a-t-on besoin de collections immuables ?
Question naturelle : si les collections mutables sont si flexibles, pourquoi des immuables ? Les raisons sont nombreuses et touchent à la sécurité, à la lisibilité et à la prédictibilité du code.
Sécurité et prévention des erreurs
Lorsque vous exposez une collection (par exemple depuis une méthode ou une classe), vous voulez être sûr que personne ne va en modifier le contenu par inadvertance. C’est particulièrement important si la collection contient des données « importantes » qui ne doivent pas bouger après l’initialisation.
Exemple :
public class Team {
private final List<String> players;
public Team(List<String> players) {
// On crée une copie immuable pour que personne ne puisse modifier la composition
this.players = List.copyOf(players);
}
public List<String> getPlayers() {
return players;
}
}
Désormais, tout code qui reçoit la liste des joueurs ne pourra pas y ajouter son « ami ».
Sécurité vis-à-vis des threads
Les collections mutables ne sont pas sûres lorsqu’elles sont consultées depuis plusieurs threads simultanément. À l’inverse, les collections immuables peuvent être partagées librement entre threads — personne ne pourra les altérer.
Débogage facilité
Si la collection ne change pas, vous savez toujours ce qu’elle contient. Pas besoin de craindre qu’elle ait été modifiée « en douce » ailleurs dans le code.
Utilisation comme clés ou valeurs dans d’autres collections
Les objets immuables sont des candidats idéaux comme clés dans une Map ou comme éléments d’un Set. Si un objet peut changer après son insertion, vous risquez d’en perdre l’accès (voir hashCode et equals).
5. Quand utiliser des collections mutables ?
Les collections mutables sont indiquées lorsque :
- La collection est construite par étapes, dans une boucle ou à partir de sources multiples.
- Des modifications fréquentes sont nécessaires : ajout, suppression, tri.
- La collection est à usage interne et aucun code « externe » ne peut l’altérer.
Exemple : construction d’une liste
List<String> shoppingList = new ArrayList<>();
shoppingList.add("Lait");
shoppingList.add("Pain");
shoppingList.add("Pommes");
// Après la construction, on peut en faire une version immuable
List<String> finalList = List.copyOf(shoppingList);
6. Quand utiliser des collections immuables ?
- Pour stocker des données constantes (par ex. la liste des jours de la semaine).
- Pour transmettre des collections entre couches de l’application (par ex. du DAO au service).
- Pour retourner des collections depuis des méthodes afin de les protéger des modifications.
- Pour des scénarios multithreads où la sécurité est importante.
Exemple : données constantes
public static final List<String> WEEKDAYS = List.of(
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
);
Exemple : exposition vers l’extérieur
public List<String> getReadOnlyNames() {
return List.copyOf(names); // personne ne pourra modifier la liste
}
7. Particularités et pièges
Immuabilité ≠ sécurité vis-à-vis des threads
Une collection immuable est protégée contre les modifications, mais cela ne signifie pas qu’elle est à l’abri de tous les problèmes en environnement multithread (par exemple si les éléments de la collection sont eux-mêmes mutables).
List<List<String>> listOfLists = List.of(new ArrayList<>());
listOfLists.get(0).add("Oops!"); // On peut modifier la liste interne !
Wrappers vs copies
Comme mentionné plus haut, Collections.unmodifiableList est un wrapper : si la collection source change, le wrapper change aussi. En revanche, List.copyOf crée une véritable copie indépendante.
List<String> base = new ArrayList<>(List.of("a", "b"));
List<String> wrap = Collections.unmodifiableList(base);
List<String> copy = List.copyOf(base);
base.add("c");
System.out.println(wrap); // [a, b, c] — a changé !
System.out.println(copy); // [a, b] — inchangé !
NullPointerException
Les méthodes factory (List.of, Set.of, Map.of) n’autorisent pas l’ajout de null :
List<String> bad = List.of("a", null); // Lève NullPointerException dès la création !
8. Comparaison des approches : avantages et inconvénients
Collections mutables
Le principal atout des collections mutables est leur flexibilité. Vous pouvez ajouter et supprimer des éléments à la volée, restructurer la collection au fil de l’évolution de la logique du programme. Cette approche est particulièrement pratique lorsqu’il faut assembler rapidement une structure temporaire ou effectuer des modifications dynamiques.
Mais cette commodité a un coût. Une collection mutable comporte toujours un risque d’intervention accidentelle : quelqu’un dans le code peut modifier les données par mégarde, entraînant des bugs difficiles à détecter. Dans les programmes multithreads, c’est encore pire — des modifications parallèles conduisent facilement à des conditions de concurrence et à des défaillances inattendues. Le cycle de vie de ces données est aussi plus difficile à contrôler : il faut constamment se souvenir qui et quand peut modifier la collection.
Collections immuables
Avec les collections immuables, on travaille plus sereinement. Vous savez exactement que personne ne les « corrigera » dans votre dos. Cela rend le code plus sûr, plus facile à déboguer et à tester, et ces collections se transmettent aisément entre threads sans verrous supplémentaires.
L’inconvénient, c’est qu’il faut parfois sacrifier un peu de performance. Pour ajouter un nouvel élément, il faut créer une nouvelle copie de la collection, ce qui implique un coût mémoire et temps supplémentaire. De plus, construire de grandes structures directement en immuable peut être peu pratique. En général, on les assemble d’abord dans une collection mutable temporaire, puis on les « fige » quand tout est prêt.
9. Erreurs typiques
Erreur n° 1 : Retourner une collection mutable vers l’extérieur. Si vous retournez depuis une méthode un ArrayList ordinaire, n’importe quel code externe peut ajouter ou supprimer des éléments. Cela peut conduire à des bugs très difficiles à retracer.
Erreur n° 2 : Utiliser un wrapper au lieu d’une copie. Si vous utilisez Collections.unmodifiableList mais que la collection d’origine est modifiée ailleurs, l’« immuabilité » n’est qu’une illusion.
Erreur n° 3 : Objets mutables à l’intérieur de collections immuables. Même si la collection est immuable, ses éléments peuvent être mutables. Cela peut entraîner des changements d’état inattendus.
Erreur n° 4 : Essayer d’ajouter null dans une collection créée via des méthodes factory. Contrairement aux anciennes collections, les nouvelles méthodes factory n’autorisent pas null — vous aurez une NullPointerException.
Erreur n° 5 : S’attendre à une implémentation concrète. Les collections créées via List.of ou Set.of ne garantissent pas le type d’implémentation (ce n’est pas nécessairement ArrayList ou HashSet). Ne vous y fiez pas.
GO TO FULL VERSION