Record patterns în Java

Pattern matching Java 21 (variabile fără nume _: Java 22)

Un record pattern verifică tipul unui record și îi desface direct componentele în variabile, inclusiv pentru records imbricate.

Sintaxă
if (o instanceof Punct(int x, int y)) { ... }

switch (forma) {
    case Cerc(Punct(var x, var y), double r) -> ...
    case Linie(Punct a, _) -> ...      // _ = nu mă interesează
}

Ce face Record patterns

Un record pattern arată ca apelul constructorului, dar face invers: Punct(int x, int y) testează dacă obiectul e un Punct și, dacă da, pune componentele în x și y. Apare oriunde apar pattern-uri: în instanceof și în case.

Tiparele se pot imbrica: Linie(Punct(var x1, var y1), Punct(var x2, var y2)) desface două niveluri dintr-o dată. În locul tipului poți scrie var, iar pentru componentele care nu te interesează folosești _ (Java 22), care nu declară nicio variabilă.

Împreună cu interfețele sealed, record patterns permit un stil „date + funcții”: definești variantele ca records și scrii operațiile ca switch-uri complete, verificate de compilator. Ca în orice switch cu tipare, poți adăuga condiții cu when.

Exemple

Exemplul 1: Desfacere în instanceof și în switch

Componentele x și y sunt disponibile direct, fără apeluri p.x(). În switch, condiția when folosește variabilele extrase.

record Punct(int x, int y) { }

public class Main {
    static String cadran(Object o) {
        return switch (o) {
            case Punct(int x, int y) when x == 0 && y == 0 -> "originea";
            case Punct(int x, int y) when x > 0 && y > 0 -> "cadranul I";
            case Punct(int x, int y) when x < 0 && y > 0 -> "cadranul II";
            case Punct(int x, int y) when x < 0 && y < 0 -> "cadranul III";
            case Punct(int x, int y) when x > 0 && y < 0 -> "cadranul IV";
            case Punct p -> "pe o axă";
            default -> "nu e punct";
        };
    }

    public static void main(String[] args) {
        Object o = new Punct(3, -4);
        if (o instanceof Punct(int x, int y)) {
            System.out.println("distanța de la origine: " + Math.sqrt(x * x + y * y));
        }
        Object[] puncte = {new Punct(0, 0), new Punct(2, 5), new Punct(-1, 7), new Punct(-2, -2), o, new Punct(0, 9), "Iași"};
        for (Object p : puncte) {
            System.out.println(p + " -> " + cadran(p));
        }
    }
}

Rezultat

distanța de la origine: 5.0
Punct[x=0, y=0] -> originea
Punct[x=2, y=5] -> cadranul I
Punct[x=-1, y=7] -> cadranul II
Punct[x=-2, y=-2] -> cadranul III
Punct[x=3, y=-4] -> cadranul IV
Punct[x=0, y=9] -> pe o axă
Iași -> nu e punct

Exemplul 2: Tipare imbricate, var și _

Comenzile conțin un client și o adresă. Switch-ul desface două niveluri; componentele nefolosite sunt marcate cu _.

sealed interface Livrare { }
record Adresa(String oras, String strada) { }
record Curier(String client, Adresa adresa, double greutate) implements Livrare { }
record Ridicare(String client, String magazin) implements Livrare { }

public class Main {
    static String eticheta(Livrare l) {
        return switch (l) {
            case Curier(var client, Adresa(var oras, _), var kg) when kg > 30 ->
                "marfă grea pentru " + client + " (" + oras + "), " + kg + " kg";
            case Curier(var client, Adresa(var oras, var strada), _) ->
                "curier: " + client + ", " + strada + ", " + oras;
            case Ridicare(var client, var magazin) ->
                client + " ridică din magazinul " + magazin;
        };
    }

    public static void main(String[] args) {
        Livrare[] livrari = {
            new Curier("Ana Pop", new Adresa("Iași", "Str. Lăpușneanu 12"), 2.5),
            new Curier("Ion Rusu", new Adresa("Galați", "Str. Brăilei 5"), 45),
            new Ridicare("Maria Ene", "Palas"),
        };
        for (Livrare l : livrari) {
            System.out.println(eticheta(l));
        }
    }
}

Rezultat

curier: Ana Pop, Str. Lăpușneanu 12, Iași
marfă grea pentru Ion Rusu (Galați), 45.0 kg
Maria Ene ridică din magazinul Palas

Exemplul 3: Calcul recursiv pe o expresie

O expresie aritmetică este fie un număr, fie o sumă, fie un produs. Funcția evalueaza desface fiecare nod și se apelează recursiv pe părți.

sealed interface Expr { }
record Numar(int valoare) implements Expr { }
record Suma(Expr stanga, Expr dreapta) implements Expr { }
record Produs(Expr stanga, Expr dreapta) implements Expr { }

public class Main {
    static int evalueaza(Expr e) {
        return switch (e) {
            case Numar(int v) -> v;
            case Suma(Expr a, Expr b) -> evalueaza(a) + evalueaza(b);
            case Produs(Expr a, Expr b) -> evalueaza(a) * evalueaza(b);
        };
    }

    static String text(Expr e) {
        return switch (e) {
            case Numar(int v) -> String.valueOf(v);
            case Suma(Expr a, Expr b) -> "(" + text(a) + " + " + text(b) + ")";
            case Produs(Expr a, Expr b) -> text(a) + " * " + text(b);
        };
    }

    public static void main(String[] args) {
        Expr e = new Produs(new Suma(new Numar(2), new Numar(3)), new Numar(4));
        System.out.println(text(e) + " = " + evalueaza(e));
    }
}

Rezultat

(2 + 3) * 4 = 20

Greșeli frecvente

  • Pui alt număr de componente decât are record-ul: incorrect number of nested patterns.
  • Scrii un tip greșit pentru o componentă (Punct(double x, double y) pentru componente int). Folosește tipul exact sau var.
  • Folosești record patterns pe o clasă obișnuită. Desfacerea funcționează doar pentru records.

Comenzi înrudite

  • record: record declară o clasă imutabilă pentru date: constructorul, accesorii, equals, hashCode și toString sunt generați automat.
  • Pattern matching (instanceof și switch): Pattern matching testează tipul (și forma) unui obiect și îi extrage părțile într-un singur pas, în instanceof sau în switch.
  • sealed, permits și non-sealed: O clasă sau interfață sealed își alege explicit subtipurile (permits); fiecare subtip este final, sealed sau non-sealed.
  • instanceof: instanceof verifică dacă un obiect este de un anumit tip; în forma nouă declară și o variabilă deja convertită la acel tip.