Baza Wiedzy

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.

Kurs Java

    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ść.

    pattern matching instanceof dziedziczenie

    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.

    Najlepszy newsletter o Javie w Polsce

    Czy chcesz otrzymywać nowości ze świata Javy oraz przykładowe pytania rekrutacyjne? Zapisz się na newsletter i bądź na bieżąco! Otrzymasz także ekskluzywne materiały oraz informacje o nowych kursach i promocjach.

    Nikomu nie udostępniamy Twojego maila, a jeśli zechcesz to w każdej chwili możesz się wypisać.

    Komentarze do artykułu

    Wyłączyliśmy możliwość dodawania komentarzy. Poniżej znajdziesz archiwalne wpisy z czasów gdy strona była jeszcze hobbystycznym blogiem. Zapraszamy natomiast do zadawnia pytań i dyskusji na naszej grupe na facebooku.