CodeGym /Cours /JAVA 25 SELF /Collections immuables : Collections.unmodifiable

Collections immuables : Collections.unmodifiable

JAVA 25 SELF
Niveau 33 , Leçon 2
Disponible

1. Le problème de la mutabilité des collections

En Java, les collections ressemblent à un entrepôt de marchandises : n’importe qui peut venir ajouter, enlever, modifier quelque chose. Parfois c’est pratique, mais dans les grands programmes cela devient un casse-tête. Imaginez que vous ayez transmis une liste de produits depuis votre classe vers l’extérieur, et que quelqu’un en supprime la moitié. Ou — encore plus « amusant » — dans une application multithread, un thread ajoute des éléments pendant qu’un autre les lit : le résultat peut être inattendu et les erreurs difficiles à traquer (par exemple, ConcurrentModificationException).

Voici un exemple montrant pourquoi la mutabilité des collections est une source de bogues :

import java.util.*;

public class Inventory {
    private List<String> products = new ArrayList<>();

    public Inventory() {
        products.add("Thé");
        products.add("Café");
    }

    public List<String> getProducts() {
        // DANGEREUX ! Nous renvoyons une référence à la liste interne
        return products;
    }
}
public class Main {
    public static void main(String[] args) {
        Inventory inv = new Inventory();
        List<String> external = inv.getProducts();
        external.remove("Thé"); // Oups! Il n'y a plus de thé dans l'inventaire
        System.out.println(inv.getProducts()); // [Café]
    }
}

Vous voyez le piège ? Une méthode renvoie la collection interne, une autre la modifie. On peut ainsi détruire par inadvertance des données qui devraient être protégées.

2. Créer des collections immuables : Collections.unmodifiable*

Pour éviter ce genre de désagréments, Java propose une protection efficace : on peut rendre une collection « non modifiable » via des vues spéciales fournies par la classe Collections :

  • Collections.unmodifiableList(list)
  • Collections.unmodifiableSet(set)
  • Collections.unmodifiableMap(map)

Comment cela fonctionne-t-il ? Vous créez d’abord une collection ordinaire, puis vous l’enveloppez dans une vue « non modifiable » :

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> drinks = new ArrayList<>();
        drinks.add("Thé");
        drinks.add("Café");

        List<String> immutableDrinks = Collections.unmodifiableList(drinks);

        System.out.println(immutableDrinks); // [Thé, Café]

        // Essayons d'ajouter un élément
        immutableDrinks.add("Cacao"); // Bam! UnsupportedOperationException
    }
}

Toute tentative de modifier une telle collection provoque le déclenchement d’une UnsupportedOperationException. C’est comme si vous colliez une énorme étiquette "NE PAS TOUCHER!" sur la boîte — et toute personne qui essaiera d’ajouter ou de supprimer quelque chose se fera taper sur les doigts (ou sur la pile d’appels).

Exemple : protéger l’état interne

Corrigeons notre classe Inventory de l’exemple précédent :

import java.util.*;

public class Inventory {
    private List<String> products = new ArrayList<>();

    public Inventory() {
        products.add("Thé");
        products.add("Café");
    }

    public List<String> getProducts() {
        // Désormais, nous renvoyons une vue
        return Collections.unmodifiableList(products);
    }
}

Désormais, si quelqu’un essaie de modifier la liste obtenue, il obtiendra une exception.

3. Comportement des collections immuables : protection superficielle

Important à comprendre : unmodifiableList et ses « frères » ne font qu’ajouter une vue autour de la collection d’origine. Elles ne créent pas de copie — toute modification de la collection d’origine (celle « à l’intérieur ») sera visible aussi dans la vue !

Démonstration

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> drinks = new ArrayList<>();
        drinks.add("Thé");
        List<String> immutableDrinks = Collections.unmodifiableList(drinks);

        drinks.add("Café"); // Nous modifions la collection d'origine
        System.out.println(immutableDrinks); // [Thé, Café] — l'élément est apparu !
    }
}

Conclusion : la vue protège seulement contre les modifications via la vue elle-même. Si quelqu’un détient une référence à la collection d’origine, il peut toujours la modifier.

4. Immuabilité profonde : mythes et réalités

Les vues unmodifiable* rendent la collection non modifiable seulement de l’extérieur. Mais si la collection contient des objets mutables, on peut les modifier !

Exemple

import java.util.*;

class Product {
    String name;
    Product(String name) {
        this.name = name;
    }
    public String toString() {
        return name;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Product> products = new ArrayList<>();
        products.add(new Product("Thé"));
        List<Product> immutableProducts = Collections.unmodifiableList(products);

        // Nous modifions l'objet contenu dans la collection
        immutableProducts.get(0).name = "Café";
        System.out.println(immutableProducts); // [Café]
    }
}

Conclusion :

  • La collection est « immuable », mais pas les objets qu’elle contient.
  • Pour une immuabilité complète (profonde), utilisez des objets immuables (par exemple, String, Integer, record-classes, ou rendez vos propres classes immuables).

5. Quand utiliser des collections immuables

Pour protéger l’état interne

Si vous écrivez une classe qui stocke une collection et que vous l’exposez à l’extérieur, renvoyez toujours une vue afin que personne ne puisse modifier vos données par accident (ou volontairement) :

public List<String> getProducts() {
    return Collections.unmodifiableList(products);
}

Dans les programmes multithread

Dans les applications multithread, les collections mutables sont une source de problèmes (race condition, ConcurrentModificationException et autres « joies de la vie »). Si la collection n’a pas besoin d’être modifiée après sa création, rendez-la immuable.

Pour transmettre des données entre couches

Si vous transmettez une collection d’une couche de l’application à une autre (par exemple, d’un DAO à un service), passez une copie immuable ou une vue — cela protège contre les modifications accidentelles.

6. Exemples pratiques

Exemple 1 : protéger la liste des étudiants

import java.util.*;

public class Group {
    private final List<String> students = new ArrayList<>();

    public void addStudent(String name) {
        students.add(name);
    }

    public List<String> getStudents() {
        return Collections.unmodifiableList(students);
    }
}

Ainsi, personne ne pourra ajouter ou supprimer un étudiant directement via getStudents().

Exemple 2 : carte (Map) immuable

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> grades = new HashMap<>();
        grades.put("John", 5);
        grades.put("Mary", 4);

        Map<String, Integer> immutableGrades = Collections.unmodifiableMap(grades);

        // immutableGrades.put("Peter", 3); // UnsupportedOperationException
    }
}

7. Subtilités utiles

Alternatives modernes : List.of, Set.of, Map.of

Avec Java 9, des moyens encore plus pratiques de créer des collections immuables sont apparus :

List<String> drinks = List.of("Thé", "Café");
Set<String> fruits = Set.of("Pomme", "Banane");
Map<String, Integer> ages = Map.of("John", 20, "Mary", 21);
  • Ces collections sont immuables (toute tentative de modification — exception).
  • Elles n’ont pas de collection « d’origine » mutable (contrairement à Collections.unmodifiable*).
  • Elles n’acceptent pas la valeur null.

Avec Java 10, il existe aussi des méthodes de copie : List.copyOf, Set.copyOf, Map.copyOf — elles créent une copie immuable de la collection fournie.

Comparaison des façons de créer des collections immuables

Méthode Immuabilité profonde Peut-on modifier la collection d'origine ? Accepte null ? Version de Java
Collections.unmodifiableList(list)
Non Oui Oui 1.2
List.of(...), Set.of(...), Map.of(...)
Non Non (pas de collection d'origine) Non 9+

8. Erreurs courantes avec les collections immuables

Erreur n° 1 : modifier la collection d’origine après avoir créé la vue. Vous avez créé une unmodifiableList, puis quelqu’un modifie la liste d’origine. La vue ne protège pas contre cela — les modifications seront visibles partout où la vue est utilisée.

Erreur n° 2 : s’attendre à une immuabilité profonde. Beaucoup pensent que si la collection est immuable, les objets qu’elle contient ne peuvent pas être modifiés non plus. En réalité, seule la structure est protégée (ajout/suppression/modification via la collection), pas le contenu des objets.

Erreur n° 3 : utiliser des collections immuables avec la valeur null dans les usines modernes. Les collections créées via List.of, Set.of, Map.of n’acceptent pas null. Toute tentative d’ajouter ou de recevoir null entraînera une exception.

Erreur n° 4 : exposer des références à des collections mutables. Si vous renvoyez à l’extérieur une référence à une collection interne (sans vue), vous perdez le contrôle de vos données — voie directe vers les bogues et la violation des invariants.

Erreur n° 5 : utiliser des collections immuables dans du code qui s’attend à de la mutabilité. Si du code tiers essaie de modifier la collection (par exemple, ajouter un élément), il obtiendra une UnsupportedOperationException. Assurez-vous que le code client est au courant de l’immuabilité des données.

1
Mission
JAVA 25 SELF, niveau 33, leçon 2
Bloqué
Création d'une liste protégée de livres rares dans la bibliothèque 📚
Création d'une liste protégée de livres rares dans la bibliothèque 📚
1
Mission
JAVA 25 SELF, niveau 33, leçon 2
Bloqué
Menu dynamique au restaurant : comment les modifications des recettes influent sur la version destinée aux invités 🍽️
Menu dynamique au restaurant : comment les modifications des recettes influent sur la version destinée aux invités 🍽️
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION