Classe Utility Java Collections
Usa la classe utility Collections in Java per ordinare, cercare, invertire, mescolare e incapsulare le collezioni.
java.util.Collections è il contenitore di helper statici che operano sulle collezioni della libreria standard. Pensala come già pensi a java.util.Arrays: una classe final senza stato di istanza, solo metodi statici. Non scrivi mai new Collections() — scrivi Collections.sort(list), Collections.shuffle(list), Collections.unmodifiableMap(map).
È facile confondere la classe con l'interfaccia accanto a cui si trova: Collection<E> (interfaccia, con la C maiuscola e senza s) è il supertipo di List, Set e Queue; Collections (classe, al plurale) è la cassetta degli attrezzi utility. La classe non implementa l'interfaccia; semplicemente opera sulle collezioni che lo fanno.
Un tour guidato della cassetta degli attrezzi
I metodi si raggruppano in sei temi. Li toccheremo tutti, con i due capitoli successivi che approfondiscono specificatamente l'ordinamento e la ricerca.
1. Ordinamento e riordinamento
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 indicessort è un mergesort stabile — gli elementi uguali mantengono il loro ordine relativo. shuffle esegue un Fisher-Yates shuffle, che è uniformemente casuale quando lo è l'RNG. rotate è ciò che vuoi quando intendi "sposta tutto di N posizioni, avvolgendo attorno alle estremità". reverse, swap e rotate mutano la lista in place; nessuno di essi restituisce qualcosa di utile.
2. Ricerca
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 ha il suo capitolo dedicato — in breve: la lista deve essere già ordinata nello stesso ordine usato dalla ricerca, e un valore di ritorno negativo significa "non trovato, ma puoi calcolare il punto di inserimento come -result - 1".
3. Riempimento, copia, sostituzione
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();Le factory empty/singleton/nCopies restituiscono istanze cached e immutabili — non allocano ad ogni chiamata. Sono una piccola ottimizzazione gratuita quando hai bisogno di una collezione nota come vuota o molto piccola.
4. Wrapper sincronizzati (per lo più storici)
List<String> lockedList = Collections.synchronizedList(new ArrayList<>());
Map<String, Int> lockedMap = Collections.synchronizedMap(new HashMap<>());
Set<String> lockedSet = Collections.synchronizedSet(new HashSet<>());Questi incapsulano una collezione in modo che ogni metodo acquisisca un lock sul wrapper. Si applica lo stesso avviso di Hashtable: le operazioni composte sono ancora soggette a race condition, e gli iteratori devono essere racchiusi esplicitamente in blocchi synchronized (wrapper) { ... }:
synchronized (lockedList) {
for (String s : lockedList) { ... } // safe: holds the lock for the whole walk
}Nel codice moderno preferisci ConcurrentHashMap, CopyOnWriteArrayList e ConcurrentSkipListSet. I wrapper sincronizzati esistono per adattare un'API non thread-safe a una thread-safe quando non c'è altra soluzione.
5. Wrapper non modificabili
List<String> frozen = Collections.unmodifiableList(mutableList);
Set<String> frozenS = Collections.unmodifiableSet(mutableSet);
Map<K, V> frozenM = Collections.unmodifiableMap(mutableMap);Questi incapsulano una collezione in modo che i metodi di mutazione lancino UnsupportedOperationException. La collezione originale è ancora mutabile — il wrapper è una vista di sola lettura. Le modifiche attraverso l'originale appaiono attraverso la vista. Questa è una differenza fondamentale rispetto alle factory List.of(...) / Set.of(...) / Map.of(...) che producono collezioni completamente immutabili supportate dal proprio storage. Il prossimo capitolo confronta i due approcci.
6. Viste per singolo elemento e type-safe
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 latercheckedList, checkedSet, checkedMap installano un controllo di tipo a runtime su ogni inserimento. Utile nel codice legacy che passa collezioni generiche attraverso API tipizzate come Object — il wrapper fallisce immediatamente al punto di inserimento invece che molto più tardi al punto di recupero.
Alcuni metodi piccoli ma di grande valore
Collections.disjoint(a, b)restituiscetruese nessun elemento diaè inb. Idiomatico per "c'è qualsiasi sovrapposizione tra questi due set?"Collections.frequency(coll, target)conta le occorrenze — molto più chiaro dicoll.stream().filter(x -> x.equals(target)).count().Collections.nCopies(n, x)è a volte esattamente ciò di cui hai bisogno, es.result.addAll(Collections.nCopies(rows, "pad")). La lista restituita è immutabile ma consuma O(1) memoria indipendentemente dan— è una lista virtuale, non un array backing.Collections.reverse(list)è in-place e stabile. Non reinventarla con un ciclofor.Collections.addAll(coll, "a", "b", "c")è più breve e veloce dicoll.addAll(List.of("a", "b", "c"))perché evita la lista intermedia.
Cosa Collections non è
- Non un sostituto di Stream. Per filter/map/reduce, usa gli stream.
Collectionsopera su mutazioni e query dirette, non su pipeline dichiarative. - Non il posto per
List.of/Set.of/Map.of. Quelle sono factory sulle interfacce, aggiunte in Java 9. Si trovano accanto aCollections.unmodifiableListma non fanno parte di questa classe. - Non il posto per i collector degli stream. Quello è
java.util.stream.Collectors. Pacchetto diverso, ruolo diverso.
Un esempio concreto: la cassetta degli attrezzi in un unico programma
Il programma seguente applica una dozzina di metodi Collections a una singola lista e una singola mappa per rendere l'API tangibile: sort, reverse, shuffle, rotate, swap, binarySearch, min/max, frequency, disjoint, fill, replaceAll e la vista non modificabile.
Cosa ricavare dall'esecuzione:
- Ogni metodo o muta in place (
sort,reverse,shuffle,rotate,swap,fill,replaceAll) o restituisce una risposta primitiva (min,max,frequency,disjoint,binarySearch). Nulla nella cassetta degli attrezzi restituisce una "nuova" lista ordinata —Collections.sortmodifica quella che gli hai passato. binarySearchha restituito l'indice di"delta"e un valore negativo per"zeta". La convenzione-result - 1fornisce il punto di inserimento che manterrebbe la lista ordinata.replaceAllha riscritto una stringa ovunque apparisse;fillha sovrascritto ogni slot. Entrambi lavorano sulla stessa lista — utile quando vuoi riciclare lo storage.Collections.unmodifiableList(backing)ha restituito una vista di sola lettura. La vista ha lanciato un'eccezione suadd, ma mutare la lista backing ha funzionato ancora, e il cambiamento è apparso attraverso la vista. La vista non è una copia.
Cosa c'è dopo
La cassetta degli attrezzi è ora nella tua testa a livello di indice. Due operazioni meritano uno sguardo più attento perché i loro dettagli sono importanti: Ordinamento delle Collezioni Java (quando usare Collections.sort vs List.sort vs stream().sorted(), ordine stabile, builder di comparator, specializzazioni per tipi primitivi) e Ricerca nelle Collezioni Java (contains, indexOf, binarySearch e ricerca basata sugli stream). Il prossimo capitolo riguarda l'ordinamento.