# Día 5: El Collection Framework y Genéricos en Java 17 y 21

> List, Set, Map, Queue y Deque, Sequenced Collections en Java 21 y rendimiento asintótico

- Plan: [JavaMastery](https://aprende-java.webs-economicas.es/), día 5 de 30
- Fase 1: Java Vanilla — El Núcleo del Lenguaje (17 & 21)
- Dedicación: 4h
- Profundidad: para dominar
- Revisado: 2026-10-29
- Versión interactiva: https://aprende-java.webs-economicas.es/dias/colecciones-genericos/

## Al terminar este día sabrás

- Elegir la colección adecuada sabiendo su coste real, no solo su notación O.
- Usar la nueva API de Sequenced Collections de Java 21 y su equivalente en Java 17.
- Explicar el borrado de tipos y aplicar comodines con la regla PECS.
- Distinguir entre colecciones inmutables, vistas no modificables y copias.


## Conceptos

### 1. ArrayList vs LinkedList: Rendimiento Real frente a Teoría

ArrayList usa un array continuo en memoria que aprovecha la caché L1/L2 de la CPU. LinkedList usa nodos dispersos con punteros que degradan el rendimiento por continuos cache misses.

**Cómo funciona por dentro.**

`ArrayList` guarda las referencias en un array contiguo. Acceder por índice es O(1) y recorrerla aprovecha la caché de la CPU, porque los elementos están juntos en memoria. Cuando se llena, crea un array 1,5 veces mayor y copia, lo que reparte el coste (O(1) amortizado por inserción al final). Insertar en medio exige desplazar elementos con `System.arraycopy`, una operación muy optimizada.

`LinkedList` crea un nodo por elemento con punteros al anterior y al siguiente. Cada nodo ocupa unos 24-32 bytes adicionales y está disperso por el heap, así que recorrerla provoca fallos de caché constantemente. La inserción en medio es O(1) solo si ya tienes el nodo; llegar hasta él cuesta O(n).

Conclusión práctica: usa `ArrayList` por defecto, y si sabes el tamaño aproximado, pásalo al constructor. Si necesitas una cola o pila, usa `ArrayDeque`, no `LinkedList` ni la antigua `Stack`.

*Elecciones por defecto razonables*

```java
// Lista: ArrayList con capacidad inicial si conoces el tamaño
List<TareaDto> dtos = new ArrayList<>(entidades.size());
for (var e : entidades) dtos.add(TareaMapper.aDto(e));

// Cola FIFO y pila LIFO: ArrayDeque (sin nodos, array circular)
Deque<String> cola = new ArrayDeque<>();
cola.offerLast("a");
cola.offerLast("b");
cola.pollFirst();                 // "a"

Deque<String> pila = new ArrayDeque<>();
pila.push("x");
pila.push("y");
pila.pop();                       // "y"

// Borrar mientras recorres: removeIf evita ConcurrentModificationException
dtos.removeIf(TareaDto::completada);
```

- **En una línea:** `List<String> elementos = new ArrayList<>(100);` con capacidad inicial reservada.
- **Por qué importa:** En el 98% de los desarrollos reales de Java, ArrayList es drásticamente más rápido que LinkedList incluso para inserciones intermedias moderadas.
- **Error típico:** Usar LinkedList "porque voy a insertar elementos", ignorando que cada nodo gasta 24 bytes extra de punteros en memoria.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** Memoria de la CPU, Complejidad O(1) vs O(n), Listas

### 2. Sequenced Collections (La Gran Novedad de Java 21)

Una jerarquía de interfaces introducida en Java 21 que estandariza el acceso al primer y último elemento de cualquier colección ordenada sin trucos.

**Cómo funciona por dentro.**

Antes de Java 21, cada colección ordenada tenía su propia forma de acceder a los extremos: `list.get(0)` y `list.get(list.size() - 1)`, `deque.getFirst()`, `sortedSet.first()` y `last()`, y `LinkedHashSet` directamente no ofrecía forma de obtener el último sin recorrerlo entero.

Java 21 (JEP 431) añade tres interfaces a la jerarquía. `SequencedCollection` aporta `getFirst()`, `getLast()`, `addFirst()`, `addLast()`, `removeFirst()`, `removeLast()` y `reversed()`. `SequencedSet` hace lo mismo para conjuntos con orden de encuentro, y `SequencedMap` añade `firstEntry()`, `lastEntry()`, `pollFirstEntry()`, `putFirst()` y `reversed()`.

`reversed()` devuelve una vista, no una copia: recorrerla no cuesta memoria extra y los cambios en la vista se reflejan en la colección original. `List`, `Deque`, `LinkedHashSet`, `SortedSet`, `LinkedHashMap` y `SortedMap` implementan estas interfaces automáticamente.

*Misma operación en Java 17 y en Java 21*

```java
List<String> pasos = new ArrayList<>(List.of("compilar", "probar", "empaquetar"));

// Java 17
String primero17 = pasos.get(0);
String ultimo17  = pasos.get(pasos.size() - 1);      // IndexOutOfBounds si está vacía
List<String> alReves17 = new ArrayList<>(pasos);
Collections.reverse(alReves17);                       // copia + inversión

// Java 21
String primero = pasos.getFirst();                    // NoSuchElementException si está vacía
String ultimo  = pasos.getLast();
for (String p : pasos.reversed()) { /* vista invertida, sin copiar */ }

// LinkedHashMap como SequencedMap: útil para cachés LRU sencillas
var recientes = new LinkedHashMap<String, Instant>();
recientes.putLast("dia-3", Instant.now());
recientes.putFirst("dia-1", Instant.now());
Map.Entry<String, Instant> masAntigua = recientes.pollFirstEntry();
```

- **En una línea:** `lista.getFirst(); lista.getLast(); var invertida = lista.reversed();`
- **Por qué importa:** Resuelve una inconsistencia histórica de Java donde cada colección (List, Deque, SortedSet) usaba métodos distintos para acceder a sus extremos.
- **Error típico:** Intentar llamar a `.getFirst()` en un proyecto corporativo compilado con Java 17.
- **Java 17 frente a 21:** EXCLUSIVO DE JAVA 21 LTS. En Java 17 se usa `lista.get(lista.size() - 1)`.
- **Se conecta con:** Collection Framework, Java 21 LTS, Ordenamiento

### 3. Set y Map: Hash, Linked y Tree (y Claves Inmutables)

`HashSet` y `HashMap` no garantizan orden y ofrecen O(1); `LinkedHashSet` y `LinkedHashMap` conservan el orden de inserción; `TreeSet` y `TreeMap` mantienen los elementos ordenados (O(log n)) con `Comparable` o un `Comparator`. En todos ellos, las claves no deben mutar después de insertarlas.

**Cómo funciona por dentro.**

Las tres familias comparten interfaz y se diferencian en el orden y el coste. `HashMap` y `HashSet` guardan los elementos en una tabla de cubos según su `hashCode`: inserción y búsqueda en O(1) de media, pero el orden de recorrido es arbitrario y puede cambiar al crecer la tabla. `LinkedHashMap` y `LinkedHashSet` añaden una lista doblemente enlazada que recuerda el orden de inserción (o el de acceso, útil para cachés LRU con `removeEldestEntry`). `TreeMap` y `TreeSet` usan un árbol rojo-negro ordenado por `compareTo` o por un `Comparator`: O(log n), pero con operaciones de rango como `headMap`, `ceilingKey` o `subSet`.

Cada familia exige algo distinto de la clave: las de hash usan `equals` y `hashCode`; las de árbol, la comparación, que debe ser coherente con `equals` (si `compare` devuelve 0, el `TreeSet` considera que son el mismo elemento y descarta el segundo).

En todas, la clave no debe cambiar mientras esté dentro. Un `HashMap` calcula el cubo de una clave al insertarla; si después mutas un campo que participa en `hashCode`, la entrada se queda en el cubo antiguo y cualquier búsqueda va al nuevo: `get` devuelve `null` y la entrada queda huérfana. Usa claves inmutables (`String`, enums, records con componentes inmutables) o el identificador de la entidad, nunca la entidad mutable.

*Elegir mapa según el orden que necesitas, y la clave que se pierde*

```java
var porHash = new HashMap<String, Integer>();             // orden arbitrario, O(1)
var porInsercion = new LinkedHashMap<String, Integer>();  // orden de inserción
var porNombre = new TreeMap<String, Integer>();           // orden alfabético, O(log n)

// Caché LRU de 100 elementos con LinkedHashMap en orden de acceso
var lru = new LinkedHashMap<String, Tarifa>(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Tarifa> mayor) {
        return size() > 100;
    }
};

// TreeMap: consultas por rango
var ventas = new TreeMap<LocalDate, BigDecimal>();
var octubre = ventas.subMap(LocalDate.of(2026, 10, 1), LocalDate.of(2026, 11, 1));

// Clave mutable: la entrada se pierde dentro del mapa
class Coordenada {
    int x, y;
    Coordenada(int x, int y) { this.x = x; this.y = y; }
    @Override public boolean equals(Object o) { return o instanceof Coordenada c && c.x == x && c.y == y; }
    @Override public int hashCode() { return Objects.hash(x, y); }
}
var mapa = new HashMap<Coordenada, String>();
var c = new Coordenada(1, 2);
mapa.put(c, "tesoro");
c.x = 99;
System.out.println(mapa.get(c));                          // null: busca en otro cubo
// Solución: record Coordenada(int x, int y) {}
```

- **En una línea:** `new TreeMap<>(Comparator.comparing(Cliente::apellido))` o un `record ClaveTarea(String codigo)` como clave.
- **Por qué importa:** Elegir la implementación adecuada evita ordenar a mano y los bugs de orden impredecible. Una clave que muta dentro de un mapa se vuelve inaccesible.
- **Error típico:** Usar un objeto mutable como clave de un `HashMap` y modificarlo después: la entrada queda huérfana dentro del mapa.
- **Java 17 frente a 21:** En Java 21, `LinkedHashMap`, `TreeMap`, `LinkedHashSet` y `TreeSet` implementan las interfaces Sequenced (`getFirst()`, `reversed()`).
- **Se conecta con:** equals y hashCode, Comparable y Comparator, Records como claves

### 4. Borrado de Tipos (Type Erasure) en Genéricos

Los genéricos (`List<String>`) solo existen en el código fuente para que el compilador verifique tipos. Al compilar a bytecode, el tipo se borra y se sustituye por Object.

**Cómo funciona por dentro.**

Los genéricos de Java se comprueban al compilar y después se borran: en el bytecode, `List<String>` y `List<Integer>` son el mismo `List`. El compilador inserta los casts necesarios y, cuando hace falta, métodos puente para que la herencia funcione. Se diseñó así para mantener compatibilidad con el código anterior a Java 5.

Consecuencias directas: no puedes hacer `new T()` ni `new T[10]`, ni `instanceof List<String>`, ni sobrecargar dos métodos que solo difieren en el parámetro genérico. Para recuperar el tipo en tiempo de ejecución se pasa un `Class<T>` explícito, como hacen Jackson o Spring.

Los comodines resuelven otra limitación: `List<Integer>` no es subtipo de `List<Number>`. La regla PECS (Producer Extends, Consumer Super) indica qué usar: si solo lees de la colección (te produce valores) usa `? extends T`; si solo escribes en ella (consume tus valores) usa `? super T`.

*Borrado de tipos y PECS en la práctica*

```java
// Mismo tipo en ejecución
System.out.println(new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass()); // true

// No se puede instanciar T: se pide la clase o una fábrica
<T> T crear(Class<T> tipo) throws ReflectiveOperationException {
    return tipo.getDeclaredConstructor().newInstance();
}
<T> List<T> repetir(Supplier<T> fabrica, int n) {
    return Stream.generate(fabrica).limit(n).toList();
}

// PECS: origen produce (extends), destino consume (super)
static <T> void copiar(List<? extends T> origen, List<? super T> destino) {
    for (T elemento : origen) destino.add(elemento);
}

List<Integer> enteros = List.of(1, 2, 3);
List<Number> numeros = new ArrayList<>();
copiar(enteros, numeros);        // compila gracias a los comodines

double sumar(Collection<? extends Number> valores) {
    return valores.stream().mapToDouble(Number::doubleValue).sum();
}
```

- **En una línea:** En el bytecode, `List<Tarea>` se transforma en `List` sin tipo.
- **Por qué importa:** Explica por qué no puedes hacer `new T()` ni `if (obj instanceof List<String>)` en tiempo de ejecución.
- **Error típico:** Intentar inspeccionar el tipo genérico de una lista en tiempo de ejecución mediante `lista.getClass()`.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** Bytecode, Compilador javac, Reflexión

### 5. Colecciones Inmutables con List.of(), Set.of() y Map.of()

Factorías que crean colecciones inmutables compactas y de alto rendimiento. No admiten valores `null`.

**Cómo funciona por dentro.**

Hay tres cosas que a menudo se confunden. `List.of`, `Set.of` y `Map.of` (Java 9) crean colecciones realmente inmutables: cualquier modificación lanza `UnsupportedOperationException`, no admiten `null` y están optimizadas en memoria. `List.copyOf` crea una copia inmutable de otra colección, y si ya es inmutable devuelve la misma. `Stream.toList()` (Java 16) también devuelve una lista no modificable.

`Collections.unmodifiableList(lista)` es distinto: devuelve una vista de solo lectura sobre la lista original. Tú no puedes modificarla a través de la vista, pero si alguien modifica la original, la vista refleja el cambio. Sirve para exponer el estado interno sin copiarlo, sabiendo que no es una foto fija.

`Set.of` y `Map.of` además rechazan duplicados al crearse, y el orden de iteración de `Set.of` y `Map.of` no está definido y cambia entre ejecuciones a propósito, para que nadie dependa de él.

*Inmutable, vista y copia*

```java
List<String> original = new ArrayList<>(List.of("a", "b"));

List<String> vista = Collections.unmodifiableList(original);
List<String> copia = List.copyOf(original);

original.add("c");
System.out.println(vista);   // [a, b, c]  -> la vista ve el cambio
System.out.println(copia);   // [a, b]     -> la copia es una foto fija

copia.add("d");              // UnsupportedOperationException
List.of("x", null);          // NullPointerException al crear
Set.of("A", "A");            // IllegalArgumentException: duplicate element

// Exponer estado interno sin permitir modificarlo desde fuera
class Proyecto {
    private final List<String> miembros = new ArrayList<>();
    public List<String> miembros() { return Collections.unmodifiableList(miembros); }
    public void anadirMiembro(String m) { miembros.add(m); }  // única vía de cambio
}
```

- **En una línea:** `List<String> estados = List.of("PENDIENTE", "EN_PROCESO", "FINALIZADA");`
- **Por qué importa:** Protegen el estado interno de servicios impidiendo que otras clases añadan o quiten elementos.
- **Error típico:** Intentar llamar a `.add()` sobre una lista creada con `List.of()`, recibiendo `UnsupportedOperationException`.
- **Java 17 frente a 21:** Totalmente estándar en 17 y 21.
- **Se conecta con:** Inmutabilidad, Seguridad Concurrente, Clean Architecture

### 6. Queue, Deque e Iterator: Colas, Pilas y Recorridos Seguros

`Queue` es una cola FIFO (`offer`, `poll`, `peek`); `Deque` permite añadir y quitar por los dos extremos, así que sirve como cola y como pila (`push`, `pop`). `ArrayDeque` es la implementación recomendada para ambas. `Iterator` recorre una colección y es la forma segura de borrar elementos mientras la recorres.

**Cómo funciona por dentro.**

`Queue` modela una cola: se añade por el final y se saca por el principio (FIFO). Cada operación tiene dos versiones: `add`, `remove` y `element` lanzan excepción si no pueden; `offer`, `poll` y `peek` devuelven `false` o `null`. `Deque` permite operar por los dos extremos, así que la misma estructura sirve de cola (`offerLast` y `pollFirst`) y de pila (`push` y `pop`). `ArrayDeque`, basada en un array circular, es la implementación recomendada para los dos usos; `Stack` y `Vector` son clases de Java 1.0, sincronizadas y con un diseño heredado. `PriorityQueue` saca siempre el elemento menor según su orden, útil para planificar trabajo por prioridad.

Un `Iterator` recorre una colección con `hasNext` y `next`, y es lo que usa el for-each por debajo. Las colecciones normales son fail-fast: si se modifican mientras se recorren por otro camino que no sea el propio iterador, el siguiente `next` lanza `ConcurrentModificationException`. Para borrar mientras recorres, usa `iterator.remove()` o, mejor, `removeIf` con un predicado.

`ConcurrentModificationException` no tiene que ver necesariamente con hilos: casi siempre aparece en código de un solo hilo que borra dentro de un for-each.

*Cola, pila, prioridad y borrado seguro*

```java
// Cola FIFO de trabajos
Queue<Trabajo> cola = new ArrayDeque<>();
cola.offer(new Trabajo("A"));
cola.offer(new Trabajo("B"));
Trabajo siguiente = cola.poll();                 // A (null si está vacía)

// Pila LIFO para deshacer cambios
Deque<Cambio> historial = new ArrayDeque<>();
historial.push(cambio1);
historial.push(cambio2);
Cambio ultimo = historial.pop();                 // cambio2

// Prioridad: siempre sale la tarea que vence antes
var porVencimiento = new PriorityQueue<>(Comparator.comparing(Tarea::vence));

// Borrar mientras se recorre
for (var t : tareas) {
    if (t.estaHecha()) tareas.remove(t);         // ConcurrentModificationException
}
tareas.removeIf(Tarea::estaHecha);               // correcto y legible

for (Iterator<Tarea> it = tareas.iterator(); it.hasNext(); ) {
    if (it.next().estaHecha()) it.remove();      // correcto si necesitas más control
}
```

- **En una línea:** `Deque<String> pila = new ArrayDeque<>(); pila.push("a"); pila.pop();` y `tareas.removeIf(Tarea::estaHecha);`
- **Por qué importa:** `Stack` y `Vector` son clases antiguas y sincronizadas; `ArrayDeque` las sustituye. Y borrar dentro de un for-each lanza `ConcurrentModificationException`.
- **Error típico:** Hacer `lista.remove(x)` dentro de un `for (var x : lista)`: lanza `ConcurrentModificationException`. Usa `iterator.remove()` o `lista.removeIf(...)`.
- **Java 17 frente a 21:** En Java 21, `Deque` también es una `SequencedCollection` (`getFirst()`, `reversed()`).
- **Se conecta con:** Recorridos BFS y DFS, ConcurrentModificationException, PriorityQueue

**Compruébalo:** ¿Por qué `ArrayDeque` y no `Stack` para implementar una pila?

`Stack` hereda de `Vector`: todos sus métodos están sincronizados (coste innecesario en un solo hilo) y expone operaciones de lista como `get(i)` que rompen la idea de pila. `ArrayDeque` es más rápida y su API (`push`, `pop`, `peek`) es la de una pila.


## Sesión de 90 minutos

### Bloque 1: Anatomía de Colecciones (0 - 25 min)

- **Qué estudiar:** Jerarquía de Collection, List, Set, Map y diferencias de rendimiento.
- **Pregunta a responder:** ¿Por qué ArrayList redimensiona aumentando un 50% su capacidad?
- **Entregable:** Benchmark casero con System.nanoTime() comparando lectura secuencial en ArrayList vs LinkedList.

### Bloque 2: Novedades Java 21 vs Java 17 (25 - 50 min)

- **Qué estudiar:** Sequenced Collections: `getFirst()`, `getLast()`, `reversed()`.
- **Pregunta a responder:** ¿Cómo escribir código compatible con Java 17 y qué ganas al migrar a Java 21?
- **Entregable:** Ejemplo de manipulación de listas y sets usando la sintaxis de 17 y la de 21.

### Bloque 3: Comparadores y Conjuntos Ordenados (50 - 80 min)

- **Qué estudiar:** Uso de `Comparator.comparing(...)` y ordenamiento de colecciones complejas.
- **Pregunta a responder:** ¿Cómo ordenar por dos criterios sucesivos (precio descendente, luego nombre alfabético)?
- **Entregable:** Suite de tests JUnit verificando el ordenamiento exacto de una lista de 5 productos.

### Cierre y Rúbrica (80 - 90 min)

- **Qué estudiar:** Autoevaluación con las preguntas anti-memorización.
- **Pregunta a responder:** Repasar mentalmente las 3 reglas de oro de colecciones.
- **Entregable:** Registro del progreso y katas completadas en la base de datos de estudio.


## Explícalo desde cero

### Por qué ArrayList casi siempre supera a LinkedList

- **Qué entender:** La memoria de las computadoras modernas premia la localidad espacial (datos contiguos en el array). Recorrer un ArrayList permite a la CPU precargar líneas de caché contiguas, mientras que LinkedList salta por punteros dispersos en el Heap.
- **Qué suele explicarse mal:** Quedarse en la teoría de O(1) frente a O(n) sin explicar el impacto del hardware y la memoria caché de la CPU.
- **Ejemplo sencillo:** Leer las páginas seguidas de un libro físico encuadernado (ArrayList) frente a hacer una búsqueda del tesoro donde cada pista te manda a una habitación distinta de la casa (LinkedList).
- **Señal de alerta:** Si recomiendas LinkedList por defecto para cualquier lista en una entrevista técnica.


## Preguntas

### Nivel 1 (Básico): ¿Por qué `Set.of("A", "A")` lanza una excepción al arrancar mientras que `new HashSet<>(List.of("A", "A"))` no?

`Set.of()` prohíbe explícitamente duplicados en su declaración y lanza `IllegalArgumentException` inmediatamente para avisarte de un error de lógica de diseño.

**Respuesta razonada:** `Set.of` está pensado para declarar constantes y valida sus argumentos al crearse: si recibes dos elementos iguales, asume que es un error de programación y lanza `IllegalArgumentException` en vez de esconderlo. Así detectas pronto, por ejemplo, un copiar y pegar en una lista de roles.

`new HashSet<>(List.of("A", "A"))` hace otra cosa: la lista sí admite duplicados, y el constructor de `HashSet` va añadiendo elementos; `add` simplemente devuelve `false` para el repetido. Ese es el comportamiento que quieres cuando deduplicas datos que vienen de fuera.

### Nivel 2 (Intermedio): ¿Qué método introdujo Java 21 para obtener el último elemento de una lista y cómo se hacía en Java 17?

En Java 21 se usa `lista.getLast()`. En Java 17 se usaba `lista.get(lista.size() - 1)`.

**Respuesta razonada:** Java 21 añadió `getLast()` a `List` gracias a la interfaz `SequencedCollection` (junto con `getFirst()`, `removeLast()` y `reversed()`).

En Java 17 se hacía `lista.get(lista.size() - 1)`, comprobando antes que no estuviera vacía para evitar `IndexOutOfBoundsException`. En otras colecciones dependía del tipo: `deque.peekLast()`, `sortedSet.last()`, y para un `LinkedHashSet` había que recorrerlo entero. La mejora de Java 21 es precisamente unificar todo eso.

### Nivel 3 (Entrevista Senior): ¿Por qué `ConcurrentHashMap` no admite claves o valores `null`, a diferencia de `HashMap` tradicional?

Porque en entornos multihilo, si `map.get(key)` devuelve `null`, es ambiguo si la clave no existe o si la clave existía asociada al valor null. En un HashMap secuencial se desempata con `containsKey()`, pero en entornos concurrentes el estado pudo cambiar entre ambas llamadas.

**Respuesta razonada:** Por ambigüedad en concurrencia. En un `HashMap`, si `get(clave)` devuelve `null` puedes llamar a `containsKey` para saber si la clave no existe o si existe con valor `null`. En un `ConcurrentHashMap`, entre esas dos llamadas otro hilo puede haber cambiado el mapa, así que la comprobación no es fiable. Prohibiendo `null`, `get` devolviendo `null` significa siempre «no está».

Además, operaciones atómicas como `putIfAbsent`, `computeIfAbsent` o `merge` usan `null` como señal de ausencia. Doug Lea, autor de la clase, lo explicó así: en mapas concurrentes esa ambigüedad no se puede tolerar.


## Reto del tutor

**Reto:** Un candidato explica: "Para devolver una lista de tareas desde mi servicio, devuelvo directamente mi campo privado `return this.tareas;`". Evalúa el riesgo arquitectónico y la corrección idiomática en Java 17/21.

- **Lo que está bien:** Nota que se está retornando la lista solicitada.
- **Respuesta superficial:** Decir simplemente que "es mejor usar un getter".
- **Lo que falta de fondo:** No percibir la fuga de encapsulamiento: el llamante recibe la referencia original y puede vaciar la lista con `tareas.clear()` desde afuera sin que el servicio se entere.
- **Respuesta senior:** Es una fuga crítica de encapsulamiento. Debe devolverse una copia inmutable o una vista de sólo lectura: en Java 17 y 21 lo idiomático es `return Collections.unmodifiableList(this.tareas);` o `return List.copyOf(this.tareas);`, garantizando que ningún componente externo pueda mutar la colección interna del servicio.


## Katas

### Kata 5.1: Comparador compuesto con Comparator.comparing

Crea un `record Empleado(String departamento, String nombre, double salario)`. Escribe un método que ordene una lista por departamento alfabéticamente y, dentro de cada departamento, por salario de mayor a menor.


## Reglas de oro

- Usa `ArrayList` por defecto a menos que tengas un caso extremo demostrado.
- Jamás devuelvas referencias directas a colecciones mutables internas; usa `List.copyOf()` o `Collections.unmodifiableList()`.
- En Java 21 aprovecha `getFirst()` y `getLast()` para un código más legible y expresivo.


## Java 17 frente a Java 21

| Característica | Java 17 | Java 21 |
| --- | --- | --- |
| Acceso a extremos (`getFirst()`, `getLast()`) | Manual `lista.get(0)`, `lista.get(size-1)` | Nativo vía `SequencedCollection` |
| Colecciones invertidas (`reversed()`) | `Collections.reverse()` (muta la lista) | `.reversed()` (vista invertida sin mutar) |


## Proyecto guiado «Pedidos»: Catálogo y cola de preparación en memoria

Guardar el catálogo y los pedidos pendientes con las colecciones adecuadas para cada uso.

**Tareas:**

- Crea `CatalogoEnMemoria` con un `Map<String, Producto>` por sku y búsqueda por nombre.
- Mantén los pedidos ordenados por fecha de creación (`TreeMap` o una lista ordenada con `Comparator`).
- Crea una cola de pedidos pendientes de preparar con `ArrayDeque` y un método `siguienteAPreparar()`.

**Está listo cuando:**

- El catálogo no permite dos productos con el mismo sku.
- Los pedidos salen de la cola de preparación en el mismo orden en que se confirmaron.


## Fuentes oficiales

- [Tutorial de Java: colecciones](https://docs.oracle.com/javase/tutorial/collections/index.html)
- [Tutorial de Java: genéricos](https://docs.oracle.com/javase/tutorial/java/generics/index.html)
- [JEP 431: Sequenced Collections](https://openjdk.org/jeps/431)

