CodeGym /Cursos /JAVA 25 SELF /Record Patterns (Java 21+): sintaxis y ejemplos

Record Patterns (Java 21+): sintaxis y ejemplos

JAVA 25 SELF
Nivel 65 , Lección 3
Disponible

1. Profundizamos en las clases record

Antes de adentrarnos en record patterns, refresquemos qué es una record-clase.

Record — es una clase compacta e inmutable con métodos generados automáticamente equals, hashCode, toString, además de la creación automática de getters para todos los componentes. Las clases record aparecieron en Java 16 y fueron un auténtico regalo para quienes estaban cansados de escribir DTOs a mano.

// Clásico: Point con dos campos
record Point(int x, int y) {}

Creación del objeto y acceso a los campos:

Point p = new Point(10, 20);
System.out.println(p.x()); // 10
System.out.println(p.y()); // 20
System.out.println(p);     // Point[x=10, y=20]

Para qué sirven los Record Patterns

Antes de Java 21, si querías comprobar que un objeto era un record y obtener sus campos, había que escribir mucho código manual: comprobación de tipo con instanceof, conversión y llamada a getters.

Object obj = new Point(5, 7);
if (obj instanceof Point) {
    Point p = (Point) obj;
    int x = p.x();
    int y = p.y();
    System.out.println("x=" + x + ", y=" + y);
}

Si el record es complejo o anidado, el código se convierte rápidamente en un «bosque de desempaquetados». Y usar este reconocimiento en switch antes era incómodo.

Record patterns permiten desempaquetar los valores de las clases record directamente en el pattern matching: tanto en instanceof como en switch. Esto hace el código más corto, más seguro y más claro.

2. Sintaxis de los record patterns: el ejemplo más sencillo

record Point(int x, int y) {}

Object obj = new Point(10, 20);

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

¿Qué está pasando aquí?

  • obj instanceof Point(int x, int y) — comprobamos el tipo y desempaquetamos directamente los componentes en las variables x e y.
  • Las variables x y y solo están disponibles dentro del bloque if.

Resultado:

x=10, y=20

Atención: si obj no es Point o es null, el bloque if no se ejecutará y las variables no se declararán.

3. Record patterns en switch: concisión y seguridad

record Point(int x, int y) {}
record Circle(Point center, int radius) {}

Object shape = new Point(3, 4);

switch (shape) {
    case Point(int x, int y) -> System.out.println("Point: x=" + x + ", y=" + y);
    case Circle(Point center, int r) -> System.out.println("Circle with radius " + r);
    default -> System.out.println("Unknown shape!");
}
  • En case Point(int x, int y) obtenemos acceso inmediato a x y y.
  • En case Circle(Point center, int r) están disponibles center y r.

Resultado:

Point: x=3, y=4

Con jerarquías sealed, switch se vuelve exhaustivo: si olvidas manejar algún caso, el compilador te avisará.

4. Record patterns anidados: desempaquetado dentro de otro desempaquetado

record Point(int x, int y) {}
record Line(Point start, Point end) {}

Object obj = new Line(new Point(1, 2), new Point(3, 4));

if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
    System.out.println("Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
}

Resultado:

Line from (1,2) to (3,4)

¿Trabajas con árboles, grafos y estructuras complejas? Los patrones anidados son tus aliados.

5. Record patterns con expresiones de guarda (when)

Object obj = new Point(0, 100);

if (obj instanceof Point(int x, int y) && x == 0) {
    System.out.println("El punto está sobre el eje Y: y=" + y);
}

En switch se hace con when:

switch (obj) {
    case Point(int x, int y) when x == 0 -> System.out.println("En el eje Y: y=" + y);
    case Point(int x, int y) when y == 0 -> System.out.println("En el eje X: x=" + x);
    case Point(int x, int y) -> System.out.println("Punto normal");
    default -> System.out.println("No es un punto");
}

6. Aplicación de los record patterns en una aplicación real

Tomemos un ejemplo práctico: figuras geométricas.

record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, int width, int height) {}
public static void printShapeInfo(Object shape) {
    switch (shape) {
        case Point(int x, int y) -> System.out.println("Point: (" + x + ", " + y + ")");
        case Circle(Point(int x, int y), int radius) ->
            System.out.println("Circle: center=(" + x + ", " + y + "), radius=" + radius);
        case Rectangle(Point(int x, int y), int width, int height) ->
            System.out.println("Rectangle: topLeft=(" + x + ", " + y + "), size=" + width + "x" + height);
        default -> System.out.println("Unknown shape");
    }
}

Ejemplo de llamada:

printShapeInfo(new Rectangle(new Point(5, 10), 20, 30));

Resultado:

Rectangle: topLeft=(5, 10), size=20x30

7. Limitaciones y matices del uso de record patterns

Solo para clases record. Los record patterns funcionan únicamente con objetos que realmente son record-clases.

class NotARecord {
    int a, b;
    NotARecord(int a, int b) { this.a = a; this.b = b; }
}

// ¡Error! No es un record
// if (obj instanceof NotARecord(int a, int b)) { ... }

Coincidencia de componentes por cantidad y tipo. El patrón debe corresponder a los componentes de la clase record.

record Pair(int a, String b) {}

// Error: los tipos no coinciden
// if (obj instanceof Pair(String a, int b)) { ... }

Las variables solo están disponibles dentro del bloque. Los nombres declarados en el pattern son visibles en el cuerpo del if o case concreto en el que la coincidencia tenga éxito.

8. Patrones anidados: ejemplo con un árbol

sealed interface Expr permits NumberExpr, PlusExpr, MinusExpr {}

record NumberExpr(int value) implements Expr {}
record PlusExpr(Expr left, Expr right) implements Expr {}
record MinusExpr(Expr left, Expr right) implements Expr {}
int eval(Expr expr) {
    return switch (expr) {
        case NumberExpr(int value) -> value;
        case PlusExpr(Expr left, Expr right) -> eval(left) + eval(right);
        case MinusExpr(Expr left, Expr right) -> eval(left) - eval(right);
    };
}

Ejemplo de uso:

Expr e = new PlusExpr(new NumberExpr(7), new MinusExpr(new NumberExpr(10), new NumberExpr(3)));
System.out.println(eval(e)); // 7 + (10 - 3) = 14

Aquí desempaquetamos directamente NumberExpr(int value), PlusExpr(Expr left, Expr right) y MinusExpr(Expr left, Expr right) — el código se vuelve conciso y expresivo.

9. Tabla: comparación del pattern matching con y sin record patterns

Método Conversión de tipo manual Pattern matching con record patterns
Comprobación de tipo
if (obj instanceof ...)
if (obj instanceof Point(int x, int y))
Conversión de tipo
(Point) obj
No es necesario
Acceso a campos
p.x(), p.y()
x, y
Desempaquetado anidado Mucho código manual Patrones anidados
Uso en switch Incómodo/imposible Fácil y conciso

10. Errores habituales al usar record patterns

Error n.º 1: intentar aplicar un record pattern a una clase que no es record. Si has escrito una clase normal e intentas «desempaquetarla» como record, el compilador no lo permitirá. Los record patterns solo funcionan con auténticas record-clases.

Error n.º 2: discrepancia en el número o el tipo de componentes. El patrón debe coincidir por completo con la firma de componentes del record. Por ejemplo, para record Pair(int a, String b) no puedes escribir Pair(int x, int y).

Error n.º 3: intentar usar variables del patrón fuera del bloque. Los nombres declarados en el patrón están disponibles solo dentro del if/case correspondiente. Fuera del bloque esas variables no existen.

Error n.º 4: usar record patterns en versiones antiguas del JDK. El soporte apareció en Java 21. En Java 17 y anteriores obtendrás un error de sintaxis. Comprueba la versión del JDK y el soporte en tu IDE.

Error n.º 5: patrones anidados excesivamente profundos. Las posibilidades de anidación son potentes, pero no las compliques sin necesidad: una estructura demasiado profunda empeora la legibilidad y aumenta el riesgo de errores.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION