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
Вложенная распаковка Много ручного кода Вложенные patterns
Использование в 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: попытка использовать переменные pattern вне блока. Имена, объявленные в шаблоне, доступны только внутри соответствующего if/case. Вне блока такие переменные не существуют.

Ошибка №4: использование record patterns на старых версиях JDK. Поддержка появилась в Java 21. На Java 17 и ниже получите ошибку синтаксиса. Проверьте версию JDK и поддержку в IDE.

Ошибка №5: чрезмерно глубокие вложенные паттерны. Возможности вложения — мощные, но не усложняйте без необходимости: слишком глубокая структура ухудшает читаемость и повышает риск ошибок.

1
Задача
JAVA 25 SELF, 65 уровень, 3 лекция
Недоступна
Геометрический редактор: описание положения точек 📍
Геометрический редактор: описание положения точек 📍
1
Задача
JAVA 25 SELF, 65 уровень, 3 лекция
Недоступна
Мозговой центр калькулятора: вычисление сложных выражений 🧠➕➖
Мозговой центр калькулятора: вычисление сложных выражений 🧠➕➖
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ