W3docs

Classe utilitaire Java Collections

Utilisez la classe Collections en Java pour trier, rechercher, inverser, mélanger et encapsuler des collections.

java.util.Collections est la boîte à outils de la bibliothèque standard regroupant des méthodes statiques qui opèrent sur les collections. Pensez-y comme vous pensez à java.util.Arrays : une classe final sans état d'instance, uniquement des méthodes statiques. On n'écrit jamais new Collections() — on écrit Collections.sort(list), Collections.shuffle(list), Collections.unmodifiableMap(map).

Il est facile de confondre la classe avec l'interface qui se trouve à côté : Collection<E> (interface, avec un C majuscule et sans s) est le supertype de List, Set et Queue ; Collections (classe, au pluriel) est la boîte à outils utilitaire. La classe n'implémente pas l'interface ; elle opère simplement sur les collections qui le font.

Visite guidée de la boîte à outils

Les méthodes se regroupent en six thèmes. Nous les aborderons chacune, les deux chapitres suivants approfondissant spécifiquement le tri et la recherche.

1. Ordonnancement et réordonnancement

Collections.sort(list);                       // natural order — requires Comparable
Collections.sort(list, comparator);            // custom comparator
Collections.reverse(list);                     // in place
Collections.shuffle(list);                     // pseudo-random permutation
Collections.shuffle(list, new Random(42));     // deterministic shuffle with a seeded RNG
Collections.rotate(list, 2);                   // [a,b,c,d,e] → [d,e,a,b,c]
Collections.swap(list, 0, list.size() - 1);    // swap two indices

sort est un tri fusion stable — les éléments égaux conservent leur ordre relatif. shuffle effectue un mélange de Fisher-Yates, qui est uniformément aléatoire lorsque le générateur de nombres aléatoires l'est. rotate est ce qu'il faut utiliser quand on veut "décaler tout de N positions en faisant des rotations en bout de liste." reverse, swap et rotate mutent la liste en place ; aucun d'eux ne retourne quelque chose d'utile.

2. Recherche

int i = Collections.binarySearch(sortedList, key);              // O(log n) — list must be sorted
int j = Collections.binarySearch(sortedList, key, comparator);
T max = Collections.max(coll);
T min = Collections.min(coll, comparator);
int n  = Collections.frequency(coll, target);                   // how many times target appears
boolean disjoint = Collections.disjoint(a, b);                  // no element in common?

binarySearch a son propre chapitre — en résumé : la liste doit déjà être triée dans le même ordre que celui utilisé par la recherche, et un retour négatif signifie "non trouvé, mais vous pouvez calculer le point d'insertion comme -result - 1."

3. Remplissage, copie, remplacement

Collections.fill(list, "x");                                   // overwrite every slot with "x"
Collections.copy(dest, src);                                    // copy src into dest; dest.size() must be ≥ src.size()
Collections.replaceAll(list, "old", "new");                     // returns true if anything changed
Collections.nCopies(5, "x");                                    // immutable list with "x" 5 times
Collections.singleton(value);                                   // immutable Set of one
Collections.singletonList(value);                               // immutable List of one
Collections.singletonMap(k, v);                                 // immutable Map of one entry
Collections.emptyList();  Collections.emptyMap();  Collections.emptySet();

Les fabriques empty/singleton/nCopies retournent des instances mises en cache et immuables — elles n'allouent pas à chaque appel. C'est une petite optimisation gratuite lorsqu'on a besoin d'une collection vide connue ou d'une collection de très petite taille connue.

4. Enveloppes synchronisées (surtout historiques)

List<String>      lockedList = Collections.synchronizedList(new ArrayList<>());
Map<String, Int>  lockedMap  = Collections.synchronizedMap(new HashMap<>());
Set<String>       lockedSet  = Collections.synchronizedSet(new HashSet<>());

Celles-ci enveloppent une collection de sorte que chaque méthode acquière un verrou sur l'enveloppe. La même mise en garde que pour Hashtable s'applique : les opérations composées restent sujettes aux conditions de concurrence, et les itérateurs doivent être explicitement enveloppés dans des blocs synchronized (wrapper) { ... } :

synchronized (lockedList) {
  for (String s : lockedList) { ... }       // safe: holds the lock for the whole walk
}

Dans le code moderne, préférez ConcurrentHashMap, CopyOnWriteArrayList et ConcurrentSkipListSet. Les enveloppes synchronisées existent pour adapter une API non thread-safe en une API thread-safe quand rien d'autre ne convient.

5. Enveloppes non modifiables

List<String> frozen   = Collections.unmodifiableList(mutableList);
Set<String>  frozenS  = Collections.unmodifiableSet(mutableSet);
Map<K, V>    frozenM  = Collections.unmodifiableMap(mutableMap);

Celles-ci enveloppent une collection de sorte que les méthodes de mutation lancent UnsupportedOperationException. La collection originale reste mutable — l'enveloppe est une vue en lecture seule. Les modifications effectuées via l'original sont visibles à travers la vue. C'est une différence essentielle avec les fabriques List.of(...) / Set.of(...) / Map.of(...) qui produisent des collections entièrement immuables adossées à leur propre stockage. Le prochain chapitre compare les deux.

6. Vues mono-élément et vues typées

List<Object> objects = new ArrayList<>();
List<String> safe    = Collections.checkedList(objects, String.class);
safe.add("ok");                              // fine
((List) safe).add(42);                       // throws ClassCastException immediately, not later

checkedList, checkedSet, checkedMap installent une vérification de type à l'exécution à chaque insertion. Utile dans le code legacy qui passe des collections génériques via des API typées Object — l'enveloppe échoue bruyamment au moment de l'insertion plutôt que bien plus tard au moment de la récupération.

Quelques méthodes de petite taille mais à forte valeur ajoutée

  • Collections.disjoint(a, b) retourne true si aucun élément de a n'est dans b. Idiomatique pour "y a-t-il un chevauchement entre ces deux ensembles ?"
  • Collections.frequency(coll, target) compte les occurrences — bien plus lisible que coll.stream().filter(x -> x.equals(target)).count().
  • Collections.nCopies(n, x) est parfois exactement ce qu'il faut, par exemple result.addAll(Collections.nCopies(rows, "pad")). La liste retournée est immuable mais consomme O(1) de mémoire quelle que soit la valeur de n — c'est une liste virtuelle, pas un tableau sous-jacent.
  • Collections.reverse(list) est en place et stable. Ne réimplémentez pas votre propre version avec une boucle for.
  • Collections.addAll(coll, "a", "b", "c") est plus court et plus rapide que coll.addAll(List.of("a", "b", "c")) car il évite la liste intermédiaire.

Ce que Collections n'est pas

  • Pas un remplacement aux streams. Pour filter/map/reduce, utilisez les streams. Collections gère les mutations et les requêtes directes, pas les pipelines déclaratifs.
  • Pas l'endroit pour List.of / Set.of / Map.of. Ce sont des fabriques sur les interfaces, ajoutées dans Java 9. Elles coexistent avec Collections.unmodifiableList mais ne font pas partie de cette classe.
  • Pas l'endroit pour les collecteurs de stream. C'est java.util.stream.Collectors. Paquetage différent, rôle différent.

Un exemple concret : la boîte à outils en un seul programme

Le programme ci-dessous applique une douzaine de méthodes de Collections à une seule liste et une seule map pour rendre l'API tangible : sort, reverse, shuffle, rotate, swap, binarySearch, min/max, frequency, disjoint, fill, replaceAll et la vue non modifiable.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Chaque méthode soit mute en place (sort, reverse, shuffle, rotate, swap, fill, replaceAll) soit retourne une réponse primitive (min, max, frequency, disjoint, binarySearch). Rien dans la boîte à outils ne retourne une "nouvelle" liste triée — Collections.sort modifie celle qu'on lui a passée.
  • binarySearch a retourné l'index de "delta" et une valeur négative pour "zeta". La convention -result - 1 donne le point d'insertion qui maintiendrait la liste triée.
  • replaceAll a réécrit une chaîne partout où elle apparaissait ; fill a écrasé chaque emplacement. Les deux opèrent sur la même liste — pratique quand on veut réutiliser le stockage.
  • Collections.unmodifiableList(backing) a retourné une vue en lecture seule. La vue a lancé une exception sur add, mais muter la liste sous-jacente a fonctionné, et la modification est apparue à travers la vue. La vue n'est pas une copie.

La suite

La boîte à outils est maintenant dans votre mémoire au niveau de l'index. Deux opérations méritent un examen plus approfondi car leurs détails ont de l'importance : Trier les collections Java (quand utiliser Collections.sort vs List.sort vs stream().sorted(), ordre stable, constructeurs de comparateurs, spécialisations primitives) et Rechercher dans les collections Java (contains, indexOf, binarySearch et recherche basée sur les streams). Le prochain chapitre porte sur le tri.

Pratique

Pratique
Vous triez une `List<String>` avec `Collections.sort(list)`, puis vous appelez `Collections.binarySearch(list, 'zeta')` et le résultat est `-4`. Que signifie `-4` ?
Vous triez une `List<String>` avec `Collections.sort(list)`, puis vous appelez `Collections.binarySearch(list, 'zeta')` et le résultat est `-4`. Que signifie `-4` ?
Was this page helpful?