Clase de Utilidad Collections en Java
Usa la clase de utilidad Collections en Java para ordenar, buscar, invertir, mezclar y envolver colecciones.
java.util.Collections es la colección de métodos estáticos del auxiliar de biblioteca estándar que operan sobre colecciones. Piensa en ella como ya piensas en java.util.Arrays: una clase final sin estado de instancia, solo métodos estáticos. Nunca escribes new Collections() — escribes Collections.sort(list), Collections.shuffle(list), Collections.unmodifiableMap(map).
Es fácil confundir la clase con la interfaz junto a la que vive: Collection<E> (interfaz, con C mayúscula y sin s) es el supertipo de List, Set y Queue; Collections (clase, en plural) es la caja de herramientas de utilidades. La clase no implementa la interfaz; solo opera sobre las colecciones que sí lo hacen.
Un recorrido guiado por la caja de herramientas
Los métodos se agrupan en seis temas. Abordaremos cada uno, y los dos próximos capítulos profundizarán específicamente en ordenamiento y búsqueda.
1. Ordenamiento y reordenamiento
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 es un mergesort estable — los elementos iguales mantienen su orden relativo. shuffle realiza un shuffling de Fisher-Yates, que es uniformemente aleatorio cuando lo es el generador de números aleatorios. rotate es lo que necesitas cuando quieres decir "desplaza todo N posiciones, haciendo un envolvimiento en los extremos". reverse, swap y rotate mutan la lista en su lugar; ninguno devuelve nada útil.
2. Búsqueda
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 tiene su propio capítulo — la versión corta: la lista debe estar ya ordenada en el mismo orden que usa la búsqueda, y un valor de retorno negativo significa "no encontrado, pero puedes calcular el punto de inserción como -result - 1."
3. Relleno, copia y reemplazo
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();Las fábricas empty/singleton/nCopies devuelven instancias inmutables almacenadas en caché — no asignan memoria por llamada. Son una pequeña optimización gratuita cuando necesitas una colección vacía o muy pequeña de tamaño conocido.
4. Envolturas sincronizadas (mayormente históricas)
List<String> lockedList = Collections.synchronizedList(new ArrayList<>());
Map<String, Int> lockedMap = Collections.synchronizedMap(new HashMap<>());
Set<String> lockedSet = Collections.synchronizedSet(new HashSet<>());Estas envuelven una colección para que cada método adquiera un bloqueo sobre la envoltura. Se aplica la misma advertencia que con Hashtable: las operaciones compuestas siguen siendo propensas a condiciones de carrera, y los iteradores deben estar envueltos en bloques synchronized (wrapper) { ... } de forma explícita:
synchronized (lockedList) {
for (String s : lockedList) { ... } // safe: holds the lock for the whole walk
}En el código moderno, recurre a ConcurrentHashMap, CopyOnWriteArrayList y ConcurrentSkipListSet en su lugar. Las envolturas sincronizadas existen para adaptar una API no segura para subprocesos a una segura cuando ninguna otra opción es adecuada.
5. Envolturas no modificables
List<String> frozen = Collections.unmodifiableList(mutableList);
Set<String> frozenS = Collections.unmodifiableSet(mutableSet);
Map<K, V> frozenM = Collections.unmodifiableMap(mutableMap);Estas envuelven una colección para que los métodos de mutación lancen UnsupportedOperationException. La colección original sigue siendo mutable — la envoltura es una vista de solo lectura. Los cambios realizados a través del original se reflejan en la vista. Esa es una diferencia clave respecto a las fábricas List.of(...) / Set.of(...) / Map.of(...) que producen colecciones completamente inmutables respaldadas por su propio almacenamiento. El siguiente capítulo compara ambas.
6. Vistas de un solo elemento y tipadas de forma segura
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 instalan una verificación de tipo en tiempo de ejecución en cada inserción. Útil en código legado que pasa colecciones genéricas a través de APIs tipadas como Object — la envoltura falla de forma ruidosa en el punto de inserción en lugar de mucho más tarde en el punto de recuperación.
Algunos métodos pequeños pero de gran valor
Collections.disjoint(a, b)devuelvetruesi ningún elemento deaestá enb. Idiomático para "¿hay alguna superposición entre estos dos conjuntos?"Collections.frequency(coll, target)cuenta las ocurrencias — mucho más claro quecoll.stream().filter(x -> x.equals(target)).count().Collections.nCopies(n, x)es a veces exactamente lo que necesitas, por ejemploresult.addAll(Collections.nCopies(rows, "pad")). La lista devuelta es inmutable pero consume O(1) de memoria sin importarn— es una lista virtual, no un arreglo de respaldo.Collections.reverse(list)es en su lugar y estable. No lo reimplementes con un buclefor.Collections.addAll(coll, "a", "b", "c")es más corto y rápido quecoll.addAll(List.of("a", "b", "c"))porque evita la lista intermedia.
Lo que Collections no es
- No es un reemplazo de Stream. Para filter/map/reduce, usa streams.
Collectionsse ocupa de mutaciones y consultas directas, no de pipelines declarativos. - No es el lugar para
List.of/Set.of/Map.of. Esas son fábricas en las interfaces, añadidas en Java 9. Viven junto aCollections.unmodifiableListpero no forman parte de esta clase. - No es el lugar para los collectors de stream. Eso es
java.util.stream.Collectors. Paquete diferente, rol diferente.
Un ejemplo práctico: la caja de herramientas en un programa
El programa siguiente aplica una docena de métodos de Collections a una sola lista y un solo mapa para hacer tangible la API: sort, reverse, shuffle, rotate, swap, binarySearch, min/max, frequency, disjoint, fill, replaceAll y la vista no modificable.
Qué extraer de la ejecución:
- Cada método bien muta en su lugar (
sort,reverse,shuffle,rotate,swap,fill,replaceAll) o devuelve una respuesta primitiva (min,max,frequency,disjoint,binarySearch). Nada en la caja de herramientas devuelve una lista "nueva" ordenada —Collections.sortmodifica la que le pasaste. binarySearchdevolvió el índice de"delta"y un valor negativo para"zeta". La convención-result - 1da el punto de inserción que mantendría la lista ordenada.replaceAllreescribió una cadena en todos los lugares donde aparecía;fillsobreescribió cada posición. Ambos trabajan sobre la misma lista — útil cuando quieres reutilizar el almacenamiento.Collections.unmodifiableList(backing)devolvió una vista de solo lectura. La vista lanzó una excepción enadd, pero mutar la lista de respaldo siguió funcionando, y el cambio se reflejó a través de la vista. La vista no es una copia.
¿Qué sigue?
La caja de herramientas ya está en tu cabeza a nivel de índice. Dos operaciones merecen una mirada más detallada porque sus detalles importan: Ordenamiento de Colecciones Java (cuándo usar Collections.sort vs List.sort vs stream().sorted(), orden estable, constructores de comparadores, especializaciones primitivas) y Búsqueda en Colecciones Java (contains, indexOf, binarySearch y búsqueda basada en streams). El siguiente capítulo es ordenamiento.