Classe Utilitária Java Collections
Use a classe utilitária Collections em Java para ordenar, pesquisar, inverter, embaralhar e encapsular coleções.
java.util.Collections é o conjunto padrão da biblioteca de auxiliares estáticos que operam em coleções. Pense nela da mesma forma que você já pensa em java.util.Arrays: uma classe final sem estado de instância, apenas métodos estáticos. Você nunca escreve new Collections() — você escreve Collections.sort(list), Collections.shuffle(list), Collections.unmodifiableMap(map).
É fácil confundir a classe com a interface ao seu lado: Collection<E> (interface, com C maiúsculo e sem s) é o supertipo de List, Set e Queue; Collections (classe, plural) é a caixa de ferramentas utilitária. A classe não implementa a interface; ela apenas opera em coleções que o fazem.
Um tour guiado pela caixa de ferramentas
Os métodos se agrupam em seis temas. Abordaremos cada um, com os dois próximos capítulos aprofundando especificamente ordenação e pesquisa.
1. Ordenação e reordenação
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 é um mergesort estável — elementos iguais mantêm sua ordem relativa. shuffle executa um embaralhamento Fisher-Yates, que é uniformemente aleatório quando o RNG é. rotate é o que você quer quando significa "deslocar tudo em N posições, dando a volta nas extremidades." reverse, swap e rotate modificam a lista no lugar; nenhum deles retorna algo útil.
2. Pesquisa
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 tem seu próprio capítulo — a versão curta: a lista já deve estar ordenada na mesma ordem que a pesquisa usa, e um retorno negativo significa "não encontrado, mas você pode calcular o ponto de inserção como -result - 1."
3. Preenchimento, cópia, substituição
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();As fábricas empty/singleton/nCopies retornam instâncias em cache, imutáveis — elas não alocam por chamada. São uma pequena otimização gratuita quando você precisa de uma coleção conhecidamente vazia ou conhecidamente pequena.
4. Wrappers sincronizados (principalmente históricos)
List<String> lockedList = Collections.synchronizedList(new ArrayList<>());
Map<String, Int> lockedMap = Collections.synchronizedMap(new HashMap<>());
Set<String> lockedSet = Collections.synchronizedSet(new HashSet<>());Esses envolvem uma coleção de modo que cada método adquira um bloqueio no wrapper. A mesma ressalva de Hashtable se aplica: operações compostas ainda têm condições de corrida, e iteradores devem ser envolvidos em blocos synchronized (wrapper) { ... } explicitamente:
synchronized (lockedList) {
for (String s : lockedList) { ... } // safe: holds the lock for the whole walk
}Em código moderno, prefira ConcurrentHashMap, CopyOnWriteArrayList e ConcurrentSkipListSet. Os wrappers sincronizados existem para retrofitar uma API não segura para threads em uma segura quando nada mais se encaixa.
5. Wrappers não modificáveis
List<String> frozen = Collections.unmodifiableList(mutableList);
Set<String> frozenS = Collections.unmodifiableSet(mutableSet);
Map<K, V> frozenM = Collections.unmodifiableMap(mutableMap);Esses envolvem uma coleção de modo que métodos de mutação lancem UnsupportedOperationException. A coleção original ainda é mutável — o wrapper é uma visão somente leitura. Alterações feitas pela original aparecem na visão. Isso é uma diferença fundamental em relação às fábricas List.of(...) / Set.of(...) / Map.of(...) que produzem coleções completamente imutáveis respaldadas por seu próprio armazenamento. O próximo capítulo compara os dois.
6. Visões de elemento único e com tipo seguro
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 instalam uma verificação de tipo em tempo de execução em cada inserção. Útil em código legado que passa coleções genéricas por APIs tipadas como Object — o wrapper falha de forma audível no ponto de inserção em vez de muito depois no ponto de recuperação.
Alguns métodos pequenos, mas de alto valor
Collections.disjoint(a, b)retornatruese nenhum elemento deaestá emb. Idiomático para "há alguma sobreposição entre esses dois conjuntos?"Collections.frequency(coll, target)conta ocorrências — muito mais claro do quecoll.stream().filter(x -> x.equals(target)).count().Collections.nCopies(n, x)é às vezes exatamente o que você quer, por exemploresult.addAll(Collections.nCopies(rows, "pad")). A lista retornada é imutável, mas consome memória O(1) independentemente den— é uma lista virtual, não um array de suporte.Collections.reverse(list)é no lugar e estável. Não escreva o seu próprio com um loopfor.Collections.addAll(coll, "a", "b", "c")é mais curto e mais rápido do quecoll.addAll(List.of("a", "b", "c"))porque evita a lista intermediária.
O que Collections não é
- Não é um substituto de Stream. Para filter/map/reduce, use streams.
Collectionslida com mutação e consultas diretas, não com pipelines declarativos. - Não é o lugar para
List.of/Set.of/Map.of. Essas são fábricas nas interfaces, adicionadas no Java 9. Ficam ao lado deCollections.unmodifiableList, mas não fazem parte desta classe. - Não é o lugar para coletores de stream. Isso é
java.util.stream.Collectors. Pacote diferente, papel diferente.
Um exemplo prático: a caixa de ferramentas em um programa
O programa abaixo aplica uma dúzia de métodos de Collections a uma única lista e um único mapa para tornar a API tangível: sort, reverse, shuffle, rotate, swap, binarySearch, min/max, frequency, disjoint, fill, replaceAll e a visão não modificável.
O que extrair da execução:
- Cada método ou muta no lugar (
sort,reverse,shuffle,rotate,swap,fill,replaceAll) ou retorna uma resposta primitiva (min,max,frequency,disjoint,binarySearch). Nada na caixa de ferramentas retorna uma lista ordenada "nova" —Collections.sortmodifica a que você passou. binarySearchretornou o índice de"delta"e um valor negativo para"zeta". A convenção-result - 1fornece o ponto de inserção que manteria a lista ordenada.replaceAllreescreveu uma string em todos os lugares em que aparecia;fillsobrescreveu cada posição. Ambos operam na mesma lista — útil quando você quer reutilizar o armazenamento.Collections.unmodifiableList(backing)retornou uma visão somente leitura. A visão lançou exceção emadd, mas mutar a lista de suporte ainda funcionou, e a alteração apareceu na visão. A visão não é uma cópia.
O que vem a seguir
A caixa de ferramentas agora está em sua cabeça no nível de índice. Duas operações merecem uma análise mais detalhada porque seus detalhes importam: Ordenando Coleções Java (quando usar Collections.sort vs List.sort vs stream().sorted(), ordem estável, construtores de comparadores, especializações primitivas) e Pesquisando Coleções Java (contains, indexOf, binarySearch e pesquisa baseada em stream). O próximo capítulo é sobre ordenação.