Шаблоны записей
Шаблон записи (record pattern) не только проверяет, что объект — запись нужного типа, но и сразу разбирает её на компоненты. Вместо вызова point.x() и point.y() вы получаете готовые переменные x и y. Шаблоны записей появились в Java 21 и работают в instanceof и switch.
Разбор записи
После имени записи в скобках перечисляют шаблоны для её компонентов — в том же порядке, что и в объявлении записи:
record Point(int x, int y) {}
void main() {
Object value = new Point(3, 4);
if (value instanceof Point(int x, int y)) {
IO.println("x = " + x + ", y = " + y);
}
}Результат:
x = 3, y = 4Имена переменных в шаблоне могут отличаться от имён компонентов: важен только порядок и тип.
В switch
Особенно выразительно шаблоны записей выглядят в switch. Каждая ветка сразу получает данные нужной фигуры:
sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
String describe(Shape shape) {
return switch (shape) {
case Circle(double r) -> "круг радиусом " + r;
case Rectangle(double w, double h) when w == h -> "квадрат со стороной " + w;
case Rectangle(double w, double h) -> "прямоугольник " + w + " на " + h;
};
}
void main() {
IO.println(describe(new Circle(2)));
IO.println(describe(new Rectangle(3, 3)));
IO.println(describe(new Rectangle(2, 5)));
}Результат:
круг радиусом 2.0
квадрат со стороной 3.0
прямоугольник 2.0 на 5.0Условие when работает и здесь: оно может использовать переменные, полученные при разборе.
Такое сочетание — запечатанный интерфейс, записи и switch с шаблонами — называют программированием, ориентированным на данные (data-oriented programming). Данные описываются простыми неизменяемыми записями, а операции над ними собраны в одном switch. Если добавить новый вид фигуры, компилятор сам покажет каждый switch, который нужно дополнить.
var в шаблоне
Чтобы не повторять типы компонентов, вместо них можно написать var — компилятор выведет тип сам:
record Order(String customer, double amount) {}
void main() {
Object value = new Order("Анна", 49.90);
if (value instanceof Order(var customer, var amount)) {
IO.println(customer + " заплатила €" + amount);
}
}Результат:
Анна заплатила €49.9Вложенные шаблоны
Если компонент записи — тоже запись, шаблоны можно вкладывать друг в друга и разбирать объект на любую глубину:
record Point(int x, int y) {}
record Line(Point start, Point end) {}
void main() {
Object value = new Line(new Point(0, 0), new Point(3, 4));
if (value instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
double length = Math.hypot(x2 - x1, y2 - y1);
IO.println("Длина отрезка: " + length);
}
}Результат:
Длина отрезка: 5.0Без шаблонов пришлось бы писать line.end().x() - line.start().x() и так далее.
Безымянная переменная
Если какой-то компонент не нужен, вместо шаблона пишут _ — безымянную переменную (Java 22). Так видно, что значение пропущено намеренно:
record Order(String customer, double amount, String city) {}
void main() {
List<Order> orders = List.of(
new Order("Анна", 20.0, "Рим"),
new Order("Олег", 35.5, "Осло"));
for (Order order : orders) {
if (order instanceof Order(var name, _, var city)) {
IO.println(name + " из города " + city);
}
}
}Результат:
Анна из города Рим
Олег из города ОслоЧастая ошибка
Шаблон должен перечислять все компоненты записи. Если пропустить хотя бы один, программа не скомпилируется:
record Point(int x, int y) {}
void main() {
Object value = new Point(3, 4);
if (value instanceof Point(int x)) {
IO.println(x);
}
}Результат:
Main.java:6: error: incorrect number of nested patterns
if (value instanceof Point(int x)) {
^
required: int,int
found: int
1 error
error: compilation failedНенужные компоненты не пропускают, а заменяют на _: Point(int x, _).