Паттерн типов (type pattern) позволяет сопоставить значение или результат выражения с определенным типом. Для применения этого паттерна можно использовать оператор instanceof или выражения switch
Традиционно оператор instanceof позволял проверить принадлежность объекта определенному типа. Но в последних версиях Java при его применении также можно автоматически преобразовать объект в сопоставленный тип и передать преобразованное значение в определенную переменную. Например, пусть у нас есть следующая система типов:
// класс человека
class Person {
private String name;
String getName() {return name;}
Person(String name){ this.name=name; }
}
// служащий некоторой компании
class Employee extends Person{
private String company; // компания, где работает человек
String getCompany(){return company;}
Employee(String name, String company) {
super(name);
this.company = company;
}
}
// класс клиента банка
class Client extends Person{
private String bank; // банк клиента
String getBank(){ return bank; }
Client(String name, String bank) {
super(name);
this.bank = bank;
}
}
Рассмотрим применение паттерна типов с помощью оператора instanceof:
class Program{
public static void main(String[] args) {
Person tom = new Person("Tom");
Person bob = new Employee("Bob", "Google");
Person sam = new Client("Sam", "Sberbank");
printPerson(tom); // Person Tom
printPerson(bob); // Bob works in Google
printPerson(sam); // Sam is client of Sberbank
}
static void printPerson(Person person){
if(person instanceof Employee empl){
System.out.printf("%s works in %s\n", empl.getName(), empl.getCompany());
}
else if(person instanceof Client cl){
System.out.printf("%s is client of %s\n", cl.getName(), cl.getBank());
}
else{
System.out.println("Person " + person.getName());
}
}
}
В статическом методе printPerson получаем объект Person и с помощью оператора instanceof проверяем принадлежность объекта определенному типу. Так, выражение
person instanceof Employee empl
проверяет, представляет ли параметр person класс Employee, и если представляет (то есть оператор instanceof
возвращает true), то передает ссылку на этот объект переменной empl типа Employee. И в дальнейшем мы можем использовать эту переменную empl и производить
с ней различные операции.
Также можно использовать паттерн типов с instanceof в тернарном операторе:
class Program{
public static void main(String[] args) {
Person kate = new Client("Kate", "Sberbank");
printEmplCompany(kate); // ничего
Person sam = new Employee("Sam", "Yandex");
printEmplCompany(sam); // Yandex
Person bob = new Employee("Bob", "VK");
printEmplCompany(bob); // VK
}
static void printEmplCompany(Person p){
String company = (p instanceof Employee empl) ? empl.getCompany() : "";
System.out.println(company);
}
}
Конструкции/выражения switch позволяют упростить паттерн типов и применение проверки типов:
class Program{
public static void main(String[] args) {
Person tom = new Person("Tom");
Person bob = new Employee("Bob", "Google");
Person sam = new Client("Sam", "Sberbank");
printPerson(tom); // Person Tom
printPerson(bob); // Bob works in Google
printPerson(sam); // Sam is client of Sberbank
}
static void printPerson(Person person){
switch(person){
case Employee empl ->
System.out.printf("%s works in %s\n", empl.getName(), empl.getCompany());
case Client cl ->
System.out.printf("%s is client of %s\n", cl.getName(), cl.getBank());
default ->
System.out.println("Person " + person.getName());
}
}
}
Выражения типа case Employee empl аналогично оператору instaceof проверяют принадлежность значения типу Employee и при положительном результате передаются ссылку на объект в переменную empl.
Стоит отметить, что если сопоставляем с типом, то нам надо обязательно указать переменную. Например, мы не можем написать следующим образом:
switch(person){
case Employee ->
System.out.println("Person is Employee");
После типа Employee нам обязательно надо указать переменную. Однако в случае выше переменная не требуется - даже если мы ее определим, мы ее никак не используем. И в этом случае можно
указать прочерк _ (так называемая "неименованная переменная"):
static void printPerson(Person person){
switch(person){
case Employee _ ->
System.out.println("Person is Employee");
case Client _ ->
System.out.println("Person is Client");
default ->
System.out.println("Person " + person.getName());
}
}
Теоретически да и практически переменная может хранить значение null. При использовании оператора instanceof мы не столкнемся с проблемами, так как этот оператор возвращает
true, если выражение не равно null:
class Program{
public static void main(String[] args) {
Person tom = null;
Person bob = new Employee("Bob", "Google");
Person sam = new Client("Sam", "Sberbank");
printPerson(tom); // Person is Undefined
printPerson(bob); // Employee Bob
printPerson(sam); // Person Sam
}
static void printPerson(Person person){
if(person instanceof Employee){
System.out.println("Employee " + person.getName());
}
else if(person instanceof Person){
System.out.println("Person " + person.getName());
}
else{ // обрабатываем null
System.out.println("Person Undefined");
}
}
}
В любом случае мы можем добавить проверку на null
else if(person == null){
System.out.println("Person Undefined");
}
Но в любом случае, даже если выражение представляет тип Person, может иметь смысл сравнить его тип с типом Person, чтобы отсеить возможность нулевых ссылок:
else if(person instanceof Person){ ....
Классический оператор switch генерирует исключение NullPointerException, если выражение, которое передается оператору switch, равно null.
Этого можно избежать, добавив case null:
class Program{
public static void main(String[] args) {
Person tom = null;
Person bob = new Employee("Bob", "Google");
Person sam = new Client("Sam", "Sberbank");
printPerson(tom); // Person is Undefined
printPerson(bob); // Employee Bob
printPerson(sam); // Person Sam
}
static void printPerson(Person person){
switch(person){
case null -> // обрабатываем null
System.out.println("Person is Undefined");
case Employee _ ->
System.out.println("Employee " + person.getName());
default ->
System.out.println("Person " + person.getName());
}
}
}
При сопоставления паттернов мы можем добавлять дополнительные условия или guards, которым также должны соответствовать выражения.
Так, при использовании оператора instanceof мы можем по цепочке добавить условные выражения:
class Program{
public static void main(String[] args) {
Person bob = new Employee("Bob", "Yandex");
Person sam = new Employee("Sam", "Sberbank");
Person tom = new Person("Tom");
printPerson(bob); // Employee Bob works in Yandex
printPerson(sam); // Employee Sam
printPerson(tom); // Person Tom
}
static void printPerson(Person person){
// по цепочке используем переменную empl
if(person instanceof Employee empl && empl.getCompany() == "Yandex"){
System.out.println("Employee " + empl.getName() + " works in Yandex");
}
else if(person instanceof Employee){
System.out.println("Employee " + person.getName());
}
else if(person instanceof Person){
System.out.println("Person " + person.getName());
}
}
}
В данном случае выражение
if(person instanceof Employee empl && empl.getCompany() == "Yandex"){
System.out.println("Employee " + empl.getName() + " works in Yandex");
}
указывает, что объект person не только должен представлять тип Employee, но и его компания должна быть "Yandex". Таким образом, подвыражение empl.getCompany() == "Yandex" и
есть то, что называется "guard" или условие паттерна. И блок if выполняется, если верно это условие. Если условие не верно, то выполняется следующий блок:
else if(person instanceof Employee){
System.out.println("Employee " + person.getName());
}
ПРи использовании switch для ввода условий применяется оператор when:
class Program{
public static void main(String[] args) {
Person bob = new Employee("Bob", "Yandex");
Person sam = new Employee("Sam", "Sberbank");
Person tom = new Person("Tom");
printPerson(bob); // Employee Bob works in Yandex
printPerson(sam); // Employee Sam
printPerson(tom); // Person Tom
}
static void printPerson(Person person){
switch(person){
case Employee empl when empl.getCompany() == "Yandex" ->
System.out.println("Employee " + empl.getName() + " works in Yandex");
case Employee _ ->
System.out.println("Employee " + person.getName());
case Person _ ->
System.out.println("Person " + person.getName());
}
}
}
То есть в данном случае выражение
when empl.getCompany() == "Yandex"
И есть защитное условие паттерна.
При этом защитные условия могут быть более сложными по структуре, объединяя несколько под условий:
switch(person){
case Employee empl
when empl.getCompany() == "Yandex" && empl.name == "Bob" ->
System.out.println("Bob works in Yandex");
В данном случае блок case выполняется, если объект person представляет тип Employee, но при этом также его имя - "Bob", а компания - "Yandex"