CodeGym /Kursy /JAVA 25 SELF /Związek polimorfizmu i klas abstrakcyjnych

Związek polimorfizmu i klas abstrakcyjnych

JAVA 25 SELF
Poziom 18 , Lekcja 4
Dostępny

1. Klasy i metody abstrakcyjne

Czasem w życiu (i w programowaniu) chce się powiedzieć: „No cóż, nie wiem, jak dokładnie to się robi, ale wiem na pewno, że to musi być!”. Na przykład wszystkie zwierzęta powinny umieć wydawać dźwięk, ale jaki dokładnie — zależy od konkretnego zwierzęcia. Właśnie na takie przypadki w Javie wymyślono klasy abstrakcyjne i metody abstrakcyjne.

Klasa abstrakcyjna — to klasa, której nie można utworzyć bezpośrednio (nie można napisać new Animal(), jeśli Animal jest abstrakcyjna), ale po której można dziedziczyć. Taka klasa może zawierać zarówno zwykłe (zaimplementowane) metody, jak i abstrakcyjne — czyli zadeklarowane, ale niezaimplementowane.

Metoda abstrakcyjna — to metoda bez ciała. Deklaruje się ją za pomocą słowa kluczowego abstract i musi zostać zaimplementowana w podklasach (chyba że podklasa sama jest abstrakcyjna).

Przykład z życia

Załóżmy, że mamy aplikację dla zoo. Chcemy, aby wszystkie zwierzęta miały metodę makeSound(), ale nie wiemy, jaki dokładnie dźwięk wydają. Wtedy tworzymy klasę abstrakcyjną:

public abstract class Animal {
    public abstract void makeSound(); // Metoda abstrakcyjna
}

I konkretne zwierzęta implementują tę metodę po swojemu:

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Hau hau!");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Miau!");
    }
}

Teraz, jeśli ktoś spróbuje utworzyć new Animal(), kompilator od razu powie: „Wybacz, ale abstraktnych zwierząt w naturze nie ma!” To jest przydatne: gwarantujesz, że w programie będą istnieć tylko konkretne zwierzęta z konkretnym zachowaniem.

2. Polimorfizm poprzez abstrakcję

Abstrakcja — to wyodrębnienie wspólnego interfejsu dla grupy obiektów. Klasa abstrakcyjna właśnie wyznacza taki wspólny interfejs: mówi, jakie metody muszą zostać zaimplementowane przez wszystkich potomków.

Polimorfizm i abstrakcja działają w parze: klasa abstrakcyjna gwarantuje, że wszyscy potomkowie mają potrzebne metody, a polimorfizm pozwala wywoływać te metody przez referencję typu bazowego.

Przykład: tworzymy zoo

Zbierzmy małe zoo. Mamy abstrakcyjną klasę Animal i kilka jej potomków:

public abstract class Animal {
    public abstract void makeSound();
}
public class Cow extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Muuu!");
    }
}
public class Duck extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Kwa kwa!");
    }
}

Teraz możemy utworzyć tablicę zwierząt:

Animal[] zoo = {
    new Dog(),
    new Cat(),
    new Cow(),
    new Duck()
};

for (Animal animal : zoo) {
    animal.makeSound(); // Dla każdego zwierzęcia wywoła się "właściwa" metoda
}

Każdy obiekt w tablicy — to konkretne zwierzę, ale dla kodu to po prostu Animal. Dzięki polimorfizmowi i abstrakcji możemy być pewni, że każdy obiekt ma metodę makeSound() i że zadziała ona poprawnie.

3. Wykorzystanie klas abstrakcyjnych do polimorfizmu

Przyjrzyjmy się bardziej praktycznemu przykładowi. Wyobraźmy sobie, że tworzymy aplikację do zarządzania pracownikami firmy. Mamy różne typy pracowników: menedżerowie, programiści, testerzy. Wszyscy mają wspólną metodę work(), ale realizują ją na różne sposoby.

Abstrakcyjna klasa Employee

public abstract class Employee {
    protected String name;

    public Employee(String name) {
        this.name = name;
    }

    public abstract void work();
}

Konkretne podklasy

public class Manager extends Employee {
    public Manager(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " kieruje zespołem.");
    }
}

public class Developer extends Employee {
    public Developer(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " pisze kod.");
    }
}

public class Tester extends Employee {
    public Tester(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " testuje aplikację.");
    }
}

Zastosowanie polimorfizmu

Teraz możemy utworzyć tablicę pracowników i dla każdego wywołać metodę work():

Employee[] employees = {
    new Manager("Anna"),
    new Developer("Ivan"),
    new Tester("Mariya")
};

for (Employee e : employees) {
    e.work();
}

Wynik:

Anna kieruje zespołem.
Ivan pisze kod.
Mariya testuje aplikację.

Zwróć uwagę: nie wiemy (i nie chcemy wiedzieć!) w pętli, jaki dokładnie mamy typ pracownika. Po prostu wywołujemy work(), a każdy obiekt robi swoje.

4. Przydatne niuanse

Gwarancja implementacji metod
Klasa abstrakcyjna wymusza na wszystkich potomkach implementację wymaganych metod. Jeśli zapomnisz zaimplementować metodę abstrakcyjną w podklasie, kompilator od razu cię upomni: „Musisz to zrobić!”

Uniwersalny interfejs
Kod, który działa na tablicy lub liście typu abstrakcyjnego (Employee[], List<Animal>), może być całkowicie uniwersalny. Możesz dodawać nowe podklasy — a kod główny nie będzie wymagał zmian.

Ochrona przed „lewymi” obiektami
Ponieważ klasy abstrakcyjnej nie można utworzyć bezpośrednio, nikt przypadkiem nie stworzy obiektu „niejasnego” typu, który nie implementuje wymaganych metod.

Teoria i składnia: jak zadeklarować klasę i metodę abstrakcyjną

  • Klasę abstrakcyjną deklaruje się za pomocą słowa kluczowego abstract przed class.
  • Metodę abstrakcyjną deklaruje się za pomocą słowa kluczowego abstract i nie ma ona ciała (tylko średnik).
  • Klasa z co najmniej jedną metodą abstrakcyjną musi być abstrakcyjna.
  • Klasa dziedzicząca po klasie abstrakcyjnej musi zaimplementować wszystkie jej metody abstrakcyjne, albo sama powinna być abstrakcyjna.

Schemat

public abstract class Animal {
    public abstract void makeSound();
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Miau!");
    }
}

5. Typowe błędy przy pracy z klasami abstrakcyjnymi

Błąd nr 1: próba utworzenia obiektu klasy abstrakcyjnej.
Kod w rodzaju new Animal() się nie skompiluje. Klasy abstrakcyjne — to jak instrukcja montażu mebli bez samych części: dopóki nie pojawi się konkretny podklas, obiektu złożyć nie można.

Błąd nr 2: zapomniano zaimplementować metodę abstrakcyjną w podklasie.
Jeśli zadeklarujesz metodę abstrakcyjną, ale nie zaimplementujesz jej w potomku (i nie uczynisz tej klasy również abstrakcyjną), kompilator się „zasmuci” i pokaże błąd.

Błąd nr 3: zapomniano o modyfikatorach dostępu.
Nadpisana metoda nie może mieć bardziej restrykcyjnego modyfikatora dostępu niż w klasie bazowej. Na przykład jeśli metoda abstrakcyjna była public, to implementacja również musi być public (a nie protected ani private).

Błąd nr 4: próba zdefiniowania metody abstrakcyjnej z ciałem.
abstract-metoda nie może mieć ciała, inaczej kompilator przewróci oczami i powie: „Zdecyduj się — albo abstrakcja, albo implementacja!”

Błąd nr 5: polimorfizm nie działa dla metod statycznych.
Polimorfizm działa tylko dla metod niestatycznych. Metody statyczne nie są nadpisywane — są ukrywane, dlatego zachowanie przy wywołaniu zależy od typu zmiennej, a nie od faktycznego obiektu.

1
Ankieta/quiz
Polimorfizm i przeciążanie, poziom 18, lekcja 4
Niedostępny
Polimorfizm i przeciążanie
Polimorfizm i przeciążanie
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION