Día 5 de 30 Fase 1: Java Vanilla — El Núcleo del Lenguaje (17 & 21)

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

Para dominar. Al terminar deberías poder hacerlo sin ayuda.

  • 4h de estudio
  • 6 conceptos
  • 3 preguntas
  • 1 katas
Java 17 LTSJava 21 LTSCollectionsSequenced CollectionsGenericsHashMap InternoMapDeque
Al terminar este día sabrás 4
  • 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.

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.

En una línea List<String> elementos = new ArrayList<>(100); con capacidad inicial reservada.

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);
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.

En el 98% de los desarrollos reales de Java, ArrayList es drásticamente más rápido que LinkedList incluso para inserciones intermedias moderadas.

Se conecta con Memoria de la CPUComplejidad O(1) vs O(n)Listas

Cuatro bloques cronometrados. No pases al siguiente sin tener el resultado esperado.

  1. 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?

    Resultado esperado. Benchmark casero con System.nanoTime() comparando lectura secuencial en ArrayList vs LinkedList.

  2. 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?

    Resultado esperado. Ejemplo de manipulación de listas y sets usando la sintaxis de 17 y la de 21.

  3. 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)?

    Resultado esperado. Suite de tests JUnit verificando el ordenamiento exacto de una lista de 5 productos.

  4. 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.

    Resultado esperado. Registro del progreso y katas completadas en la base de datos de estudio.

Tres formas de comprobar si lo has entendido, de menos a más exigente: explicarlo con tus palabras, responder preguntas trampa y resolver el reto del tutor.

Explícalo desde cero

Lee cada ficha y explícala en voz alta antes de seguir: si puedes contárselo a alguien que empieza, lo has entendido.

Por qué ArrayList casi siempre supera a LinkedList

Lo que debes 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.

Analogía para contarlo

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).

Lo que 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.

Señal de que aún no lo dominas

Si recomiendas LinkedList por defecto para cualquier lista en una entrevista técnica.

Preguntas trampa

Responde de memoria, en voz alta o por escrito, y después abre la respuesta razonada: lo importante es el porqué, no la palabra exacta.

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? Ver respuesta

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.

Qué está evaluando la pregunta

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.

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? Ver respuesta

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.

Qué está evaluando la pregunta

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

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

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.

Qué está evaluando la pregunta

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.

Reto del tutor

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 una respuesta suele tener bien

Nota que se está retornando la lista solicitada.

Lo que suele quedarse en la superficie

Decir simplemente que "es mejor usar un getter".

El concepto de fondo que suele faltar

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.

Cómo lo respondería alguien senior en una entrevista

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.

Proyecto «Pedidos» · paso 5 de 30

Catálogo y cola de preparación en memoria

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

De qué va el proyecto

El backend de una tienda online: clientes, catálogo de productos y pedidos con sus líneas, pagos y envíos. Empieza como un proyecto Maven con records y termina como un sistema con API REST, seguridad, mensajería, contenedores y observabilidad. Cada paso construye sobre el anterior, así que trabaja siempre en el mismo repositorio.

Qué hacer

  1. Crea CatalogoEnMemoria con un Map<String, Producto> por sku y búsqueda por nombre.
  2. Mantén los pedidos ordenados por fecha de creación (TreeMap o una lista ordenada con Comparator).
  3. 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.

Katas: ejercicios cortos

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ísticaJava 17 LTSJava 21 LTS
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)
Se guarda automáticamente

Fuentes oficiales

Revisado el . Este día en Markdown