Начиная с версии Java 21 мы можем использовать так называемый record pattern - паттерн, который позволяет проверить на соответствие типу классу record и использовать его поля.
Для применения паттерна record можно применять как оператор instanceof, так и конструкции/выражения switch.
Пример record patter с применением оператора instanceof:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom", 22);
if(tom instanceof Person(var pName, var pAge)){
System.out.printf("Person. Name: %s; Age: %d\n", pName, pAge);
}
}
}
record Person(String name, int age){}
В данном случае с помощью выражения tom instanceof Person(var pName, var pAge) мы не только проверяем, что переменная tom представляет тип Person, но и объявляем переменные name и age, которые получают значения
одноименных полей класса Person. Затем внутри блока if мы можем использовать эти переменные.
Причем мы явным образом можем указать тип переменных
if(tom instanceof Person(String pName, int pAge)){
System.out.printf("Person. Name: %s; Age: %d\n", pName, pAge);
}
Если нам какое-то поле не нужно, мы можем его опустить и вместо переменной указать прочерк. Например, нам нужно только поле age:
if(tom instanceof Person(_, int pAge)){
System.out.printf("Person Age: %d\n", pAge);
}
Конструкции/выражения switch также позволяют использовать данный паттерн:
Person tom = new Person("Tom", 22);
switch(tom){
case Person(var pName, var pAge) ->
System.out.printf("Person. Name: %s; Age: %d\n", pName, pAge);
case null -> System.out.printf("Undefined");
}
Стоит учитывать, что в record pattern мы не можем использовать константы. Напимер, нам нужно отдельно отследить ситуацию, когда имя пользователя - "Tom". Но мы не можем написать так:
if(tom instanceof Person("Tom", _)){ // Ошибка: "Tom" - константа
System.out.printf("Person Tom");
}
Однако мы можем использовать защитные условия, где переменные сравниваются с определенными константами:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom", 22);
Person bob = new Person("Bob", 46);
printPerson(tom); // Hello Tom
printPerson(bob); // Hello
}
static void printPerson(Person person){
if(person instanceof Person(var pName, _) && pName == "Tom"){
System.out.println("Hello Tom");
}
else if(person instanceof Person(_, _) ){
System.out.println("Hello");
}
}
}
Таким образом, если имя "Tom", то срабатывает блок if:
if(person instanceof Person(var pName, _) && pName == "Tom"){
System.out.println("Hello Tom");
}
Если же у пользователя другое имя, то срабатывает блок else if
else if(person instanceof Person(_, _) ){
System.out.println("Hello");
}
При использовании switch для создания защитных условий применяется оператор when, после которого идет условие:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom", 22);
Person bob = new Person("Bob", 46);
printPerson(tom); // Hello Tom
printPerson(bob); // Hello
}
static void printPerson(Person person){
switch(person){
case Person(var pName, _) when pName == "Tom" -> // только для тех Person, где name = "Tom"
System.out.println("Hello Tom");
case Person(_, _) -> // для всех остальных Person
System.out.println("Hello");
case null -> {} // если null, ничего не выводим
}
}
}
Шаблоны могут быть вложенными:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom", 41);
Company mycorp = new Company("MyCorp", tom);
if(mycorp instanceof Company(_, Person(var name, _))){
System.out.println("CEO: " + name);
}
}
}
record Person(String name, int age){}
record Company(String name, Person ceo){}
В данном случае второе поле класса Company представляет тип Person. А с помощью выражения mycorp instanceof Company(_, Person(var name, _)) получаем поле name у класса Person в одноименную переменную name.
Аналогичный пример с switch:
public class Program{
public static void main(String[] args) {
Person tom = new Person("Tom", 41);
Company mycorp = new Company("MyCorp", tom);
switch(mycorp){
case Company(_, Person(var name, _)) ->
System.out.println("CEO: " + name);
case null -> {}
default -> {}
}
}
}
record Person(String name, int age){}
record Company(String name, Person ceo){}
Обратите внимание на последние два блока сопоставления:
case null -> {}
default -> {}
При определении конструкции следует учитывать, что варианты в ней должны быть "exhaustive" (исчерпывающими), то есть охватывать все возможные варианты. В нашем случае у нас компания может быть равна null:
Company mycorp = null;
Этот вариант отслеживается блоком
case null -> {}
Однако у нас может быть вариант, что Person равен null:
Company mycorp = new Company("MyCorp", null);
Этот вариант отслеживается блоком default, который отслеживает все остальные варианты:
default -> {}