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 |
|---|---|---|
| Проверка типа | |
|
| Приведение типа | |
Не нужно |
| Доступ к полям | |
|
| Вложенная распаковка | Много ручного кода | Вложенные 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: чрезмерно глубокие вложенные паттерны. Возможности вложения — мощные, но не усложняйте без необходимости: слишком глубокая структура ухудшает читаемость и повышает риск ошибок.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ