W3docs

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 indices

sort è 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 later

checkedList, 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) restituisce true se nessun elemento di a è in b. Idiomatico per "c'è qualsiasi sovrapposizione tra questi due set?"
  • Collections.frequency(coll, target) conta le occorrenze — molto più chiaro di coll.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 da n — è una lista virtuale, non un array backing.
  • Collections.reverse(list) è in-place e stabile. Non reinventarla con un ciclo for.
  • Collections.addAll(coll, "a", "b", "c") è più breve e veloce di coll.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. Collections opera 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 a Collections.unmodifiableList ma 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.

java— editable, runs on the server

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.sort modifica quella che gli hai passato.
  • binarySearch ha restituito l'indice di "delta" e un valore negativo per "zeta". La convenzione -result - 1 fornisce il punto di inserimento che manterrebbe la lista ordinata.
  • replaceAll ha riscritto una stringa ovunque apparisse; fill ha 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 su add, 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.

Esercitazione

Pratica
Ordini una `List<String>` con `Collections.sort(list)`, poi chiami `Collections.binarySearch(list, 'zeta')` e il risultato è `-4`. Cosa significa `-4`?
Ordini una `List<String>` con `Collections.sort(list)`, poi chiami `Collections.binarySearch(list, 'zeta')` e il risultato è `-4`. Cosa significa `-4`?
Was this page helpful?