Saltar al contenido
NextPatch Blog

5 cambios de Java 21 que deberías estar usando ya

Hilos virtuales, pattern matching en switch, case null con guardas when, record patterns y colecciones secuenciadas. Lo que cambia en cada uno, con código y un reto para practicarlo.

Java 5 min de lectura

Java 21 salió en septiembre de 2023 y muchos proyectos siguen escribiendo código como si estuvieran en Java 11. Compila igual, pero se pierden cambios que hacen el código más corto y más fácil de leer, y que te pueden preguntar en cualquier entrevista técnica.

Aquí van cinco que puedes empezar a usar el lunes. Cada uno lleva un reto de dos minutos para comprobar que lo tienes claro.

1. Hilos virtuales: escala, no velocidad

Un hilo de plataforma, el Thread de siempre, envuelve un hilo del sistema operativo: es caro de crear y hay pocos. Por eso llevamos años con pools acotados o programación asíncrona.

Java 21 cierra los hilos virtuales (JEP 444). Siguen siendo instancias de java.lang.Thread, pero los planifica la JVM sobre unos pocos hilos del sistema. Cuando uno se bloquea en una operación de E/S del JDK, se desmonta y deja libre su hilo portador para otro.

java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(task);
}

Lo que más se malinterpreta: no son hilos más rápidos. Lo que mejora es el throughput cuando tienes miles de tareas que pasan casi todo el tiempo esperando (peticiones HTTP, consultas a base de datos). Si tu carga es de CPU, no ganas nada.

Un matiz antes de usarlos en producción con Java 21: dentro de un bloque synchronized el hilo virtual queda anclado (pinning) y bloquea también a su portador. Java 24 lo resolvió con el JEP 491, pero en Java 21 conviene cambiar a ReentrantLock los synchronized que envuelven llamadas de red.

Practícalo: hilos virtuales, escala y no velocidad.

2. Pattern matching en switch

Decidir qué hacer según el tipo de un objeto era una cadena de if ... else if con instanceof. Java 21 cierra el pattern matching para switch (JEP 441), después de cuatro rondas de preview desde Java 17:

java
static String formatter(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case String s  -> String.format("String %s", s);
        default        -> obj.toString();
    };
}

El selector puede ser de cualquier tipo de referencia, y cada case lleva un patrón de tipo como los de instanceof. Gana el primer case que encaja, en el orden en que están escritos.

Lo importante es que compara el tipo real del objeto, no su valor: un Object que guarda un Long no entra en case Integer i, aunque el número quepa en un int. Además, el compilador te avisa si un case nunca se puede alcanzar porque otro anterior ya lo cubre.

Practícalo: el switch deja de comparar solo constantes.

3. case null y guardas when

Antes, un switch con selector null lanzaba NullPointerException, así que la comprobación vivía fuera, en un if. Ahora puedes meterla dentro con case null, y añadir condiciones a cada caso con when:

java
switch (s) {
    case null -> System.out.println("Oops!");
    case String v when v.equalsIgnoreCase("YES") -> System.out.println("You got it");
    case String v -> System.out.println("Sorry?");
}

Una guarda solo aplica si el patrón encaja y la condición es cierta. Si no, el switch sigue probando los siguientes case. El orden importa: el caso con guarda va antes que el general, porque al revés el general lo taparía y el compilador daría error.

Solo los patrones admiten guardas: case "hola" when ... no compila, porque una constante no es un patrón.

¿Y qué pasa con el default cuando llega un null? Te lo dejo para el reto del final.

4. Record patterns

El pattern matching de instanceof te ahorra el cast, pero luego sigues escribiendo p.x() y p.y(). Java 21 cierra los record patterns (JEP 440), que sacan los componentes en el propio patrón:

java
record Point(int x, int y) {}

if (obj instanceof Point(int x, int y)) {
    System.out.println(x + y);
}

El patrón comprueba el tipo y, si encaja, llama a los accesores del record. Los nombres de las variables no tienen que coincidir con los de los componentes, puedes usar var en cada posición y funcionan igual en instanceof y en los case de un switch. Los patrones se pueden anidar: el conjunto solo encaja si encajan todos los subpatrones, y si alguno falla, no hay excepción, simplemente no entra en esa rama.

Combinado con un switch sobre una jerarquía sealed, tratas cada forma de tus datos en una línea, sin casts y con el compilador comprobando que no se te escapa ningún caso.

Practícalo: deconstruir un record dentro del patrón.

5. Colecciones secuenciadas

Pedir el primer y el último elemento dependía del tipo: list.get(0) y list.get(list.size() - 1) en una lista, deque.getFirst() en un Deque, sortedSet.first() en un SortedSet... y en un LinkedHashSet no había forma de sacar el último sin recorrerlo entero.

El JEP 431 añade SequencedCollection, SequencedSet y SequencedMap para las colecciones con un orden definido, y las encaja en la jerarquía que ya existía:

java
String primero = lista.getFirst();
String ultimo = lista.getLast();
String ultimoDelSet = linkedHashSet.getLast();

Además de getFirst y getLast llegan addFirst, addLast, removeFirst, removeLast y reversed(). Ojo con este último: no copia la colección, devuelve una vista en orden inverso. Y con la colección vacía, getFirst() y getLast() lanzan NoSuchElementException, como ya hacía Deque.

Practícalo: getFirst, getLast y reversed.

Mini reto: el null y el default

Este código tiene un default, pero no tiene case null:

java
static String describe(Object o) {
    return switch (o) {
        case String s -> "texto";
        case Integer i -> "número";
        default -> "otra cosa";
    };
}

describe(null);

¿Qué pasa al llamar a describe(null)? ¿Devuelve "otra cosa", lanza una excepción o ni siquiera compila?

Responde en el reto de case null y guardas when. No hace falta cuenta y se resuelve en un momento.

Pasa de leerlo a practicarlo.

Un resumen de 45 segundos y un reto de dos minutos al día. La cuenta es gratis y guarda tu XP, tu racha y tus insignias.

Crear cuenta gratis