JavaRush /Курси /JAVA 25 SELF /Record patterns (Java 21+): синтаксис і приклади

Record patterns (Java 21+): синтаксис і приклади

JAVA 25 SELF
Рівень 65 , Лекція 3
Відкрита

1. Заглиблюємося у record‑класи

Перш ніж зануритися у record patterns, освіжімо в пам’яті, що таке record-клас.

Record — це компактний, незмінний клас із автоматично згенерованими методами equals, hashCode, toString, а також із автоматичним створенням геттерів для всіх компонентів. Record‑класи з’явилися в Java 16 і стали справжнім подарунком для всіх, хто втомився вручну писати «DTO‑шки».

// Класика: Point із двома полями
record Point(int x, int y) {}

Створення об’єкта та доступ до полів:

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]

Навіщо потрібні record patterns

До Java 21, якщо ви хотіли перевірити, що об’єкт — це певний record, і отримати його поля, доводилося писати багато ручного коду: перевірка типу через instanceof, приведення та виклик геттерів.

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

Якщо record складний або вкладений, код швидко перетворюється на «ліс розпакувань». До того ж використовувати таке розпізнавання в switch раніше було незручно.

Record patterns дозволяють розпаковувати значення record-класів безпосередньо у pattern matching: як у instanceof, так і в switch. Це робить код коротшим, безпечнішим і зрозумілішим.

2. Синтаксис record patterns: найпростіший приклад

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

Що тут відбувається?

  • obj instanceof Point(int x, int y) — перевіряємо тип і одразу розпаковуємо компоненти у змінні x та y.
  • Змінні x і y доступні лише всередині блоку if.

Результат:

x=10, y=20

Увага: якщо obj не Point або це null, блок if не виконається, а змінні не будуть оголошені.

3. Record patterns у switch: лаконічність і безпека

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!");
}
  • У case Point(int x, int y) ми відразу отримуємо доступ до x і y.
  • У case Circle(Point center, int r) доступні center і r.

Результат:

Point: x=3, y=4

З sealed-ієрархіями switch стає вичерпним: якщо забути обробити варіант, компілятор попередить.

4. Вкладені record patterns: розпаковка всередині розпаковки

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 + ")");
}

Результат:

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

Працюєте з деревами, графами, складними структурами? Вкладені патерни — ваші друзі.

5. Record patterns із guard‑виразами (when)

Object obj = new Point(0, 100);

if (obj instanceof Point(int x, int y) && x == 0) {
    System.out.println("Точка лежить на осі Y: y=" + y);
}

У switch це робиться за допомогою when:

switch (obj) {
    case Point(int x, int y) when x == 0 -> System.out.println("На осі Y: y=" + y);
    case Point(int x, int y) when y == 0 -> System.out.println("На осі X: x=" + x);
    case Point(int x, int y) -> System.out.println("Звичайна точка");
    default -> System.out.println("Не точка");
}

6. Застосування record patterns у реальному застосунку

Візьмемо практичний приклад — геометричні фігури.

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");
    }
}

Приклад виклику:

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

Результат:

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

7. Обмеження та нюанси використання record patterns

Лише для record‑класів. Record patterns працюють лише з об’єктами, які справді є record-класами.

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

// Помилка! Не record
// if (obj instanceof NotARecord(int a, int b)) { ... }

Відповідність компонентів за кількістю та типом. Шаблон має відповідати компонентам record‑класу.

record Pair(int a, String b) {}

// Помилка: типи не збігаються
// if (obj instanceof Pair(String a, int b)) { ... }

Змінні доступні лише всередині блоку. Імена, оголошені в pattern, видимі в тілі конкретного if або case, коли зіставлення успішне.

8. Вкладені патерни: приклад із деревом

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

Приклад використання:

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

Тут ми відразу розпаковуємо NumberExpr(int value), PlusExpr(Expr left, Expr right) і MinusExpr(Expr left, Expr right) — код стає лаконічним і виразним.

9. Таблиця: порівняння pattern matching з record patterns і без

Спосіб Приведення типу вручну Pattern matching з record patterns
Перевірка типу
if (obj instanceof ...)
if (obj instanceof Point(int x, int y))
Приведення типу
(Point) obj
Не потрібно
Доступ до полів
p.x(), p.y()
x, y
Вкладена розпаковка Багато ручного коду Вкладені патерни
Використання в switch Незручно або неможливо Легко, лаконічно

10. Типові помилки під час використання record patterns

Помилка № 1: спроба застосувати record pattern до не‑record класу. Якщо ви написали звичайний клас, а потім намагаєтеся «розпакувати» його як record, компілятор цього не дозволить. Record patterns працюють лише зі справжніми record-класами.

Помилка № 2: невідповідність кількості або типу компонентів. Шаблон має повністю відповідати сигнатурі компонентів record. Наприклад, для record Pair(int a, String b) не можна писати Pair(int x, int y).

Помилка № 3: спроба використовувати змінні патерну поза блоком. Імена, оголошені в шаблоні, доступні лише всередині відповідного if/case. Поза блоком такі змінні не існують.

Помилка № 4: використання record patterns на старих версіях JDK. Підтримка з’явилася у Java 21. У версіях Java 17 і нижче отримаєте помилку синтаксису. Перевірте версію JDK і підтримку в IDE.

Помилка № 5: надто глибокі вкладені патерни. Можливості вкладення — потужні, але не ускладнюйте без потреби: занадто глибока структура погіршує читабельність і підвищує ризик помилок.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ