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