Introducción al Collections Framework de Java
Un recorrido por el Collections Framework de Java: interfaces, implementaciones y algoritmos en java.util.
Casi todo programa termina almacenando un grupo de cosas — pedidos por enviar, palabras en un documento, usuarios conectados en este momento, la cola de tareas pendientes que procesará un hilo de trabajo. La respuesta de Java a "¿dónde pongo ese grupo?" es el Collections Framework: un conjunto coordinado de interfaces en java.util, más de una docena de implementaciones detrás de ellas, y una única clase de utilidad con algoritmos (Collections) que opera sobre las interfaces en lugar de sobre ninguna implementación concreta. Este capítulo es el mapa — qué contiene el framework, por qué tiene la forma que tiene, y qué familia se usa en qué situación. Cada capítulo de esta parte es un análisis en profundidad de una de las cajas de ese mapa.
Por qué un framework y no solo clases
Antes de Java 1.2 existían clases para grupos (Vector, Hashtable, Stack), pero no había interfaz compartida — no se podía escribir un método que aceptara "cualquier lista" y funcionar con las tres. El Collections Framework solucionó esto separando qué es un grupo (la interfaz) de cómo se almacena (la implementación). El beneficio se ve a diario:
// You program against the interface, not the class.
List<String> names = new ArrayList<>();
// Later, swap in a different implementation without touching the rest of the code:
List<String> names = new LinkedList<>();Cada método que llamas en names está definido en List. La elección de ArrayList vs. LinkedList es una decisión de rendimiento, no de API. También puedes escribir métodos como void print(List<String> xs) una sola vez y pasar cualquier implementación.
Las cuatro interfaces raíz
El framework se organiza en torno a cuatro interfaces raíz. Elige la que cuyo contrato coincida con tu dato:
Collection<E>— la raíz. Un grupo de elementos de tipoE. No se especifica nada sobre orden, duplicados ni indexación.List<E>— una colección ordenada accesible por índice. Se permiten duplicados. Piensa en "array que crece" o "secuencia enlazada."ArrayList,LinkedList,Vector.Set<E>— una colección que prohíbe duplicados. Conjunto matemático. Puede estar ordenado o no.HashSet,LinkedHashSet,TreeSet.Map<K, V>— búsqueda clave→valor. No es unaCollection(sus elementos son entradas, no valores simples), pero forma parte del framework.HashMap,LinkedHashMap,TreeMap.
Dos más se sitúan junto a List y Set como especializaciones de Collection:
Queue<E>— una colección "siguiente en la fila". FIFO por defecto.LinkedList,ArrayDeque,PriorityQueue.Deque<E>— una cola de doble extremo. Agrega y elimina por cualquiera de los dos extremos.ArrayDeque,LinkedList.
Todas las clases de colección de la biblioteca estándar implementan al menos una de estas interfaces.
Elige la familia por comportamiento, luego la clase por costo
La decisión tiene dos capas:
- ¿Qué necesita el dato? Ordenado con duplicados →
List. Sin duplicados →Set. Búsqueda por clave →Map. Productor/consumidor →QueueoDeque. - Dentro de la familia, ¿cuáles son los patrones de acceso? Acceso aleatorio por índice →
ArrayList. Muchos inserts/removes al frente →LinkedListoArrayDeque. Iteración ordenada →TreeSet/TreeMap. Solo necesito un contenedor rápido →HashSet/HashMap.
Una hoja de referencia rápida para las clases más usadas:
| Necesidad | Usa | Notas |
|---|---|---|
| Lista redimensionable, acceso aleatorio rápido | ArrayList | El List predeterminado. |
| Lista con inserciones/eliminaciones frecuentes en los extremos | ArrayDeque (como cola similar a lista) | Supera a LinkedList en la práctica. |
| Set, contains/add/remove rápido | HashSet | Sin garantía de orden. |
| Set con iteración predecible | LinkedHashSet | Iteración en orden de inserción. |
| Set ordenado | TreeSet | Operaciones O(log n). |
| Map, búsqueda rápida | HashMap | El Map predeterminado. |
| Map con iteración predecible | LinkedHashMap | Útil para cachés (LRU). |
| Map ordenado | TreeMap | Claves en orden. |
| Cola FIFO | ArrayDeque | Más rápido que LinkedList. |
| Siempre extraer el mínimo | PriorityQueue | Respaldado por un heap. |
Vector, Stack y Hashtable son las clases anteriores a la versión 1.2 — siguen en el JDK por compatibilidad con versiones anteriores, pero ya no son la elección correcta en código nuevo. Sus reemplazos modernos se cubren en sus propios capítulos.
Los generics hacen que las colecciones sean seguras en tipos
Cada interfaz y clase de colección es genérica. La parametrizas con el tipo de elemento en la declaración, y el compilador lo verifica desde ese momento:
List<String> names = new ArrayList<>();
names.add("Ada");
names.add(42); // ❌ compile error — Integer is not a String
String first = names.get(0); // no cast neededEl diamante <> a la derecha le pide al compilador que infiera el tipo a partir de la izquierda — ahorra escritura sin perder seguridad. La parte anterior del libro (Generics) es el reglamento de todo esto; el resto de esta parte asume que ya la leíste.
Los algoritmos viven en la clase Collections
Las operaciones que no están ligadas a ninguna implementación específica — ordenar, mezclar, invertir, búsqueda binaria, mínimo, máximo, frecuencia, envoltorios no modificables — viven en la clase de utilidad estática java.util.Collections. Nótese la s al final: Collection (la interfaz) es un elemento; Collections (la clase) es la caja de herramientas:
List<Integer> xs = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9, 2, 6));
Collections.sort(xs); // mutates xs
int idx = Collections.binarySearch(xs, 5);
List<Integer> ro = Collections.unmodifiableList(xs);Dedicamos tres capítulos al final de esta parte a Collections — búsqueda, ordenación y envoltorios no modificables — porque la clase de utilidad es donde ocurre gran parte del trabajo práctico.
Un primer recorrido completo
El programa siguiente toma un representante de cada familia y muestra las operaciones que usarás una y otra vez. No te preocupes por entender cada línea todavía — cada clase aquí tiene su propio capítulo. El objetivo es ver la forma del framework: los mismos nombres de métodos, los mismos patrones, el mismo modelo de iteración.
Observa tres cosas en la salida:
- El
Seteliminó silenciosamente el duplicado"Effective Java". Ese es el contrato — no se necesita código adicional por tu parte. - La
Queuedevolvió los elementos en el orden en que se agregaron.offerencola,peekmira la cabeza sin eliminarla,pollla elimina y la devuelve. - El bucle
for-eachfunciona en todaCollection. Los Maps se iteran medianteentrySet(),keySet()ovalues()según lo que necesites.
Eso es el framework en miniatura.
HashSet y HashMap no garantizan el orden de iteración — el orden que ves en la salida anterior es un detalle de implementación y puede variar entre versiones de la JVM o entre ejecuciones. Si necesitas un orden estable, usa LinkedHashSet/LinkedHashMap (orden de inserción) o TreeSet/TreeMap (orden clasificado). Nunca escribas código que dependa del orden de iteración de un HashSet o HashMap simple.
Qué sigue
Has visto el mapa. El resto de la Parte 11 recorre cada región. El punto de partida natural es la raíz de la que hereda toda colección — la propia interfaz Collection, donde métodos como add, remove, contains, size e iterator() se definen de una vez para siempre.