Pattern Matching for instanceof
Pattern Matching for instanceof to jedna z nowości, która pojawiła się w Javie 14. Stanowi część większego projektu Amber, w skład którego wchodzą także inne usprawnienia takie jak Switch Expressions, rekordy, czy Text Blocks.
Zadaniem omawianego usprawnienia, jest uproszczenie procesu sprawdzania faktycznego typu obiektu oraz jego późniejszego rzutowania. W Javie 14 funkcjonalność ta dostępna jest jako preview feature, więc należy ją włączyć przy pomocy dodatkowej flagi dodawanej podczas uruchamiania programu.
Problem do rozwiązania
Dosyć popularną sytuacją, którą możesz spotkać w kodzie, jest konieczność weryfikacji rzeczywistego typu obiektu oraz jego późniejsze rzutowanie. Może to mieć znaczenie w sytuacji, w której korzystamy z polimorfizmu jednak w pewnym momencie chcemy wykonać czynność specyficzną dla konkretnego typu obiektu.
Najpopularniejszym przypadkiem, który może tutaj przychodzić do głowy jest metoda equals(), w której możesz spotkać taki zapis:
Person.java
import java.util.Objects;
class Person {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person))
return false;
Person person = (Person) o;
return Objects.equals(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
}
Typ obiektu w metodzie equals sprawdzany jest z wykorzystaniem operatora instanceof . Jest to konieczne, ponieważ metoda ta ma zdefiniowany parametr typu Object . Oznacza to, że w jego miejsce można przekazać dowolny obiekt. Wykonanie rzutowania, czyli Person person = (Person) o, bez wcześniejszego sprawdzenia typu obiektu, przy pomocy operatora instanceof lub metody getClass() , może zakończyć się wyjątkiem ClassCastException.
Pattern Matching for instanceof
W Javie 14, zapis:
if (!(o instanceof Person))
return false;
Person person = (Person) o;
można zastąpić przy pomocy:
if (!(o instanceof Person person))
return false;
a więc cała metoda equals() może wyglądać tak:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person person))
return false;
return Objects.equals(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}
Przykład z dziedziczeniem
Nowy wersja operatora instanceof może być przydatna, gdy wykorzystujemy dziedziczenie i posługujemy się ogólnym typem referencji, a chcemy wykonać pewną operację na szczegółowym typie obiektu, np. pobrać jakąś charakterystyczną dla niego wartość.
Person.java
import java.util.Objects;
abstract class Person {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person person))
return false;
return Objects.equals(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
}
Student.java
import java.util.Objects;
class Student extends Person {
private int age;
public Student(String firstName, String lastName, int age) {
super(firstName, lastName);
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student student)) return false;
if (!super.equals(o)) return false;
return age == student.age;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), age);
}
}
Teacher.java
import java.util.Objects;
class Teacher extends Person {
private double salary;
public Teacher(String firstName, String lastName, double salary) {
super(firstName, lastName);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Teacher teacher)) return false;
if (!super.equals(o)) return false;
return Double.compare(teacher.salary, salary) == 0;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), salary);
}
}
DifferentPeople.java
class DifferentPeople {
public static void main(String[] args) {
Person[] people = new Person[4];
people[0] = new Student("Jan", "Kowalski", 24);
people[1] = new Teacher("Martyna", "Zalewska", 7000);
people[2] = new Student("Karolina", "Wadecka", 21);
people[3] = new Teacher("Andrzej", "Bartecki", 7900);
int studentMaxAge = studentMaxAge(people);
System.out.println("Najstarszy student ma " + studentMaxAge + " lat");
double avgTeacherSalary = avgSalary(people);
System.out.println("Średnia wypłata nauczycieli: " + avgTeacherSalary);
}
static int studentMaxAge(Person[] people) {
int maxAge = 0;
for (Person person : people) {
if (person instanceof Student student)
if (student.getAge() > maxAge)
maxAge = student.getAge();
}
return maxAge;
}
static double avgSalary(Person[] people) {
double sumSalary = 0;
int numberOfTeachers = 0;
for (Person person : people) {
if (person instanceof Teacher teacher) {
sumSalary += teacher.getSalary();
numberOfTeachers++;
}
}
if (numberOfTeachers != 0)
return sumSalary / numberOfTeachers;
else
return 0;
}
}
Dzięki wykorzystaniu Pattern Matching for instanceof zapis w metodach studentMaxAge() i avgSalary() może być krótszy, bez utraty czytelności.
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.