Maitriser les Collections Immuables en Java : List.of(), Set.of() et Map.of()

Guide complet sur les collections immuables en Java 9+. Apprenez a utiliser List.of(), Set.of(), Map.of() et les Multimaps pour ecrire du code thread-safe et robuste.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 7 min read
Maitriser les Collections Immuables en Java : List.of(), Set.of() et Map.of()

Introduction

L’immutabilite est un concept fondamental de la programmation fonctionnelle qui gagne en importance dans le developpement Java moderne. Depuis Java 9, le langage offre des methodes factory elegantes pour creer des collections immuables : List.of(), Set.of() et Map.of().

Pourquoi utiliser des collections immuables ?

Les collections immuables offrent plusieurs avantages majeurs :

  • Thread-safety garantie : Aucun risque de modification concurrente
  • Securite du code : Les donnees ne peuvent pas etre alterees accidentellement
  • Performance : Optimisations possibles car l’etat ne change jamais
  • Previsibilite : Le comportement du code est plus facile a comprendre et a debugger
  • Partage sur : Vous pouvez partager des references sans crainte de modifications

Dans cet article, nous explorerons en profondeur les collections immuables natives de Java, les bibliotheques tierces comme Guava et Eclipse Collections, ainsi que les Multimaps pour gerer des associations cle-valeurs multiples.


Collections Immuables en Java 9+

Les collections immuables sont des instances qui ne peuvent pas etre modifiees apres leur creation. Toute tentative de modification leve une UnsupportedOperationException.

List.of() - Listes Immuables

La methode List.of() cree une liste immuable avec un ordre d’iteration garanti.

import java.util.List;

public class ImmutableListExample {
    public static void main(String[] args) {
        // Liste vide
        List<String> emptyList = List.of();

        // Liste avec des elements
        List<String> fruits = List.of("Pomme", "Banane", "Orange", "Kiwi");

        // Acces aux elements (lecture seule)
        System.out.println(fruits.get(0)); // Pomme
        System.out.println(fruits.size()); // 4

        // Iteration
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // ATTENTION : Ceci leve une UnsupportedOperationException
        // fruits.add("Mangue"); // ERREUR !
        // fruits.remove(0);     // ERREUR !
        // fruits.set(0, "Poire"); // ERREUR !
    }
}

Set.of() - Ensembles Immuables

La methode Set.of() cree un ensemble immuable sans doublons.

import java.util.Set;

public class ImmutableSetExample {
    public static void main(String[] args) {
        // Ensemble vide
        Set<Integer> emptySet = Set.of();

        // Ensemble avec des elements uniques
        Set<Integer> numbers = Set.of(1, 2, 3, 4, 5);

        // Verification de presence
        System.out.println(numbers.contains(3)); // true
        System.out.println(numbers.contains(10)); // false

        // ATTENTION : Les doublons levent une IllegalArgumentException
        // Set<Integer> invalid = Set.of(1, 2, 2); // ERREUR a l'execution !
    }
}

Map.of() et Map.ofEntries() - Maps Immuables

Pour les maps, Java 9+ offre deux methodes selon le nombre d’entrees.

import java.util.Map;
import static java.util.Map.entry;

public class ImmutableMapExample {
    public static void main(String[] args) {
        // Map vide
        Map<String, Integer> emptyMap = Map.of();

        // Map avec jusqu'a 10 entrees (methode variadique)
        Map<String, Integer> ages = Map.of(
            "Alice", 25,
            "Bob", 30,
            "Charlie", 35
        );

        // Pour plus de 10 entrees, utilisez Map.ofEntries()
        Map<String, String> capitals = Map.ofEntries(
            entry("France", "Paris"),
            entry("Allemagne", "Berlin"),
            entry("Espagne", "Madrid"),
            entry("Italie", "Rome"),
            entry("Portugal", "Lisbonne")
        );

        // Acces aux valeurs
        System.out.println(ages.get("Alice")); // 25
        System.out.println(capitals.get("France")); // Paris

        // ATTENTION : Les cles nulles ou dupliquees levent une exception
        // Map<String, Integer> invalid = Map.of("key", 1, "key", 2); // ERREUR !
    }
}

Conversion entre Collections Mutables et Immutables

Java 10 a introduit les methodes copyOf() pour creer des copies immuables.

import java.util.*;

public class CollectionConversionExample {
    public static void main(String[] args) {
        // Collection mutable vers immuable (Java 10+)
        List<String> mutableList = new ArrayList<>();
        mutableList.add("A");
        mutableList.add("B");
        mutableList.add("C");

        // Creer une copie immuable
        List<String> immutableCopy = List.copyOf(mutableList);

        // La liste originale peut encore etre modifiee
        mutableList.add("D");
        System.out.println(mutableList);    // [A, B, C, D]
        System.out.println(immutableCopy);  // [A, B, C] - non affectee

        // Collection immuable vers mutable
        List<String> backToMutable = new ArrayList<>(immutableCopy);
        backToMutable.add("E"); // OK !

        // Meme principe pour Set et Map
        Set<String> mutableSet = new HashSet<>(Set.of("X", "Y", "Z"));
        Set<String> immutableSet = Set.copyOf(mutableSet);

        Map<String, Integer> mutableMap = new HashMap<>(Map.of("a", 1, "b", 2));
        Map<String, Integer> immutableMap = Map.copyOf(mutableMap);
    }
}

Multimaps : Plusieurs Valeurs par Cle

Les Multimaps permettent d’associer plusieurs valeurs a une meme cle. Ce pattern est tres utile pour grouper des donnees.

Guava Multimap

Google Guava offre une implementation robuste de Multimap.

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

public class GuavaMultimapExample {
    public static void main(String[] args) {
        Multimap<String, String> studentCourses = ArrayListMultimap.create();

        // Un etudiant peut avoir plusieurs cours
        studentCourses.put("Alice", "Mathematiques");
        studentCourses.put("Alice", "Physique");
        studentCourses.put("Alice", "Informatique");
        studentCourses.put("Bob", "Histoire");
        studentCourses.put("Bob", "Geographie");

        // Recuperer tous les cours d'un etudiant
        System.out.println(studentCourses.get("Alice"));
        // [Mathematiques, Physique, Informatique]

        // Nombre total d'associations
        System.out.println(studentCourses.size()); // 5

        // Nombre de cles distinctes
        System.out.println(studentCourses.keySet().size()); // 2

        // Verifier si une association existe
        System.out.println(studentCourses.containsEntry("Alice", "Physique")); // true
    }
}

Eclipse Collections BiMap

Les BiMaps permettent une recherche bidirectionnelle (cle vers valeur ET valeur vers cle).

import org.eclipse.collections.api.bimap.MutableBiMap;
import org.eclipse.collections.impl.bimap.mutable.HashBiMap;

public class BiMapExample {
    public static void main(String[] args) {
        MutableBiMap<String, String> translations = new HashBiMap<>();

        // Mapping bidirectionnel francais-anglais
        translations.put("bonjour", "hello");
        translations.put("merci", "thank you");
        translations.put("au revoir", "goodbye");

        // Recherche normale (cle -> valeur)
        System.out.println(translations.get("bonjour")); // hello

        // Recherche inverse (valeur -> cle)
        System.out.println(translations.inverse().get("hello")); // bonjour

        // Les valeurs doivent aussi etre uniques dans une BiMap
        // translations.put("salut", "hello"); // Erreur ! "hello" existe deja
    }
}

Implementation Manuelle avec Map et List

Si vous ne souhaitez pas ajouter de dependance, vous pouvez implementer un pattern similaire.

import java.util.*;

public class SimpleMultimap<K, V> {
    private final Map<K, List<V>> map = new HashMap<>();

    public void put(K key, V value) {
        map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
    }

    public List<V> get(K key) {
        return map.getOrDefault(key, Collections.emptyList());
    }

    public boolean containsKey(K key) {
        return map.containsKey(key);
    }

    public int size() {
        return map.values().stream().mapToInt(List::size).sum();
    }

    public static void main(String[] args) {
        SimpleMultimap<String, Integer> scores = new SimpleMultimap<>();
        scores.put("Player1", 100);
        scores.put("Player1", 150);
        scores.put("Player1", 200);

        System.out.println(scores.get("Player1")); // [100, 150, 200]
    }
}

Bonnes Pratiques

1. Preferez l’immutabilite par defaut

// Mauvais : collection mutable exposee
public class UserService {
    private List<User> users = new ArrayList<>();

    public List<User> getUsers() {
        return users; // Dangereux ! Le client peut modifier la liste
    }
}

// Bon : retourner une copie immuable
public class UserService {
    private List<User> users = new ArrayList<>();

    public List<User> getUsers() {
        return List.copyOf(users); // Safe !
    }
}

2. Utilisez des collections immuables pour les constantes

public class HttpStatus {
    // Constantes immuables - thread-safe et efficaces
    public static final Set<Integer> SUCCESS_CODES = Set.of(200, 201, 204);
    public static final Set<Integer> ERROR_CODES = Set.of(400, 401, 403, 404, 500);

    public static final Map<Integer, String> STATUS_MESSAGES = Map.of(
        200, "OK",
        201, "Created",
        404, "Not Found",
        500, "Internal Server Error"
    );
}

3. Documentez l’immutabilite dans vos APIs

/**
 * Retourne la liste des utilisateurs actifs.
 * @return une liste immuable des utilisateurs (ne peut pas etre modifiee)
 */
public List<User> getActiveUsers() {
    return List.copyOf(activeUsers);
}

4. Utilisez Collectors.toUnmodifiableList() avec les Streams

import java.util.stream.Collectors;

List<String> filteredNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toUnmodifiableList()); // Java 10+

Pieges Courants

Piege 1 : Les valeurs null ne sont pas acceptees

// ERREUR : NullPointerException
List<String> list = List.of("a", null, "b"); // NPE !

// Solution : filtrer les nulls ou utiliser une collection mutable
List<String> list = Arrays.asList("a", null, "b"); // Fonctionne (mutable)

Piege 2 : Les doublons dans Set.of()

// ERREUR : IllegalArgumentException
Set<Integer> set = Set.of(1, 2, 2, 3); // Exception !

// Solution : utiliser un HashSet si des doublons sont possibles
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 2, 3)); // [1, 2, 3]

Piege 3 : L’immutabilite n’est pas profonde (shallow immutability)

public class Person {
    private String name;
    // getters et setters...
}

List<Person> people = List.of(new Person("Alice"), new Person("Bob"));
// La liste est immuable, mais les objets Person sont mutables !
people.get(0).setName("Charlie"); // Fonctionne !

// Solution : rendre Person immuable aussi (record Java 16+)
public record Person(String name) {}

Piege 4 : Confusion entre unmodifiable et immutable

List<String> original = new ArrayList<>();
original.add("A");

// Collections.unmodifiableList() cree une VUE, pas une copie
List<String> unmodifiable = Collections.unmodifiableList(original);

original.add("B"); // Modifie aussi unmodifiable !
System.out.println(unmodifiable); // [A, B]

// Solution : utiliser List.copyOf() pour une vraie copie immuable
List<String> trulyImmutable = List.copyOf(original);
original.add("C");
System.out.println(trulyImmutable); // [A, B] - non affecte

Piege 5 : Limites de Map.of()

// Map.of() est limite a 10 paires cle-valeur
// Pour plus, utilisez Map.ofEntries()
Map<String, Integer> largeMap = Map.ofEntries(
    entry("k1", 1), entry("k2", 2), entry("k3", 3),
    entry("k4", 4), entry("k5", 5), entry("k6", 6),
    entry("k7", 7), entry("k8", 8), entry("k9", 9),
    entry("k10", 10), entry("k11", 11) // Plus de 10 entrees
);

Conclusion

Les collections immuables en Java sont un outil essentiel pour ecrire du code robuste, thread-safe et maintenable. Voici les points cles a retenir :

MethodeUsageDepuis
List.of()Creer une liste immuableJava 9
Set.of()Creer un ensemble immuableJava 9
Map.of()Creer une map immuable (max 10 entrees)Java 9
Map.ofEntries()Creer une map immuable (illimite)Java 9
List.copyOf()Copie immuable d’une collectionJava 10

Recommandations finales :

  1. Preferez l’immutabilite sauf si vous avez une raison specifique de modifier la collection
  2. Utilisez les methodes factory (List.of(), etc.) pour les petites collections connues a la compilation
  3. Utilisez copyOf() pour creer des copies immuables de collections existantes
  4. Attention aux nulls : les collections immuables Java ne les acceptent pas
  5. Considerez Guava ou Eclipse Collections pour des fonctionnalites avancees comme les Multimaps

En maitrisant ces concepts, vous ecrirez du code Java plus sur et plus performant.


Ressources Supplementaires

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles