W3docs

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 indices

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

checkedList, 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) devuelve true si ningún elemento de a está en b. Idiomático para "¿hay alguna superposición entre estos dos conjuntos?"
  • Collections.frequency(coll, target) cuenta las ocurrencias — mucho más claro que coll.stream().filter(x -> x.equals(target)).count().
  • Collections.nCopies(n, x) es a veces exactamente lo que necesitas, por ejemplo result.addAll(Collections.nCopies(rows, "pad")). La lista devuelta es inmutable pero consume O(1) de memoria sin importar n — es una lista virtual, no un arreglo de respaldo.
  • Collections.reverse(list) es en su lugar y estable. No lo reimplementes con un bucle for.
  • Collections.addAll(coll, "a", "b", "c") es más corto y rápido que coll.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. Collections se 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 a Collections.unmodifiableList pero 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.

java— editable, runs on the server

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.sort modifica la que le pasaste.
  • binarySearch devolvió el índice de "delta" y un valor negativo para "zeta". La convención -result - 1 da el punto de inserción que mantendría la lista ordenada.
  • replaceAll reescribió una cadena en todos los lugares donde aparecía; fill sobreescribió 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 en add, 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.

Práctica

Práctica
Ordenas un `List<String>` con `Collections.sort(list)`, luego llamas a `Collections.binarySearch(list, 'zeta')` y el resultado es `-4`. ¿Qué significa `-4`?
Ordenas un `List<String>` con `Collections.sort(list)`, luego llamas a `Collections.binarySearch(list, 'zeta')` y el resultado es `-4`. ¿Qué significa `-4`?
Was this page helpful?