Poruszone zagadnienia
- Dziedzieczenie
- Polimorfizm
Wstep
Witam w drugim odcinku serii pt. “Co Java ma wspólnego z genetyką?”
Po podróży przez świat dziedziczenia w programowaniu, wracam do Ciebie z kolejnym rozdziałem mojej opowieści. Jeśli już o dziedziczeniu mowa, to zapraszam Cię do zapoznania z tym tematem w poprzedniego wpisu tej serii javamPokaze || Co Java ma wspólnego z genetyką? || Część 1 || Dziedziczenie .
A teraz, w części drugiej, przyjrzymy się niezwykłemu zjawisku, które ponownie łączy świat Javy i genetyki. Poznaj wszechmocny
Polimorfizm
.Czym jest polimorfizm?
W dużym skrócie, to koncept, w którym obiekty różnego typu mogą być grupowane i traktowane w podobny, jednolity sposób. Co to oznacza? Jak sama nazwa wskazuje programowanie obiektowe polega na operowaniu na obiektach. Obiekty te określane są przez typ, czyli nazwe klasy, której są instancją. W polimorfizmie, jedna klasa może być traktowana jako szablon na wielu instancji zupełnie innych i różniących się od niej klas. Schemat ten kojarzy mi się z grupowaniem obiektów różnego typu w większe zbiory o wspólnych cechach, ale różniących się charakterem.
W Javie, osiągamy polimorfizm przede wszystkim poprzez dziedziczenie (kiedy klasa dziedziczy po innej klasie) oraz przesłanianie metod (kiedy klasa dziedzicząca ma swoją wersję metody, która jest związana z jej konkretnym rodzajem). W praktyce, oznacza to, że możesz napisać jeden fragment kodu, który działa dla różnych rodzajów obiektów, dzięki temu, że Java sama "rozumie", która wersja metody ma zostać użyta dla danego obiektu.
Tworzenie obiektów w Javie
Przypomnijmy sobie najpierw krok po kroku jak tworzymy obiekty w Javie:
public class Animal { } public class AnimalTest{ public static void main(String [] args){ Animal animal = new Animal(); // Tworzymy nowy obiekt typu Animal //Etapy: 1 3 2 } }
Etap 1. Deklaracja
Prosimy wirtualną maszynę Javy (
JVM
), aby zarezerowała dla nas miejsce w pamieci (na tzw. Stosie
) dla zmiennej referencyjnej typu Animal
o nazwie animal
.Etap 2. Tworzenie Obiektu
Wnioskujemy u
JVM
o rezerwacje miejsca na tzw. Stercie
dla nowego obiektu
, który tworzymy poprzez słowo kluczowe new
Etap 3. Inicjalizacja (przypisanie wartości)
Wykorzystując znak równości przypisujemy nowy
obiekt
do zmiennej referencyjnej
o nazwie animal
Jak działa polimorfizm?
Teraz popatrzmy na inny przykład z klasą
Animal
:class Animal { void makeSound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override void makeSound() { System.out.println("Dog: Woof woof!"); } } class Cat extends Animal { @Override void makeSound() { System.out.println("Cat: Meow meow!"); } } public class AnimalTest { public static void main(String[] args) { Animal[] animals = new Animal[2]; animals[0] = new Dog(); animals[1] = new Cat(); for (Animal animal : animals) { animal.makeSound(); } } }
Koncept polimorfizmu zakłada, że typ zmiennej referencyjnej oraz typ obiektu mogą być zupełnie inne. W tym kodzie utworzyliśmy jedną nadrzędną klasę
Animal
oraz subklasy Cat i Dog wydłużające Animal. Każdemu ze zwierzaków umożliwiliśmy oddanie głosu, dzięki wykorzytaniu funkcjonalności przesłonięcia @Override
(jeśli nie pamiętasz czym jest nadpisywanie/przesłonięcie metod, rzuć okiem na ten wpis javamPokaze || Java Battle || Overload vs @Override )
Klasa testowa ukazuje podstawowy sens
polimorfizmu
. W tablicy Animal umieściliśmy wszystkie zwierzaki, dzięki czemu stworzyliśmy jedną grupę, w której skład mogą wchodzić zarowno koty jak i psy wyorzystujące metodę obecną w klasie Animal według własnego uznania (pies szczeka a kot miałczy). Ten mechanizm pozwala na bardziej elastyczne podejście do tworzenia pogramów. Dzięki niemu możesz pisać ogólną logikę, która działa na wielu różnych typach obiektów, a nowe typy można łatwo dodawać, nie modyfikując istniejącego kodu.Dzieki
polimorfizmowi
możesz zdeklarować typ zmiennej referencyjnej
jako supertyp
(typ nadrzędny) i przypisać jej obiekty innego typu.Animal[] animals = new Animal[5]; animals[0] = new Dog(); animals[1] = new Cat(); animals[2] = new Butterfly(); animals[3] = new Tiger();
Kolejny przykład kodu:
interface MathematicalOperation { double execute(double a, double b); } public class Addition implements MathematicalOperation { @Override public double execute(double a, double b) { return a + b; } } public class Subtraction implements MathematicalOperation { @Override public double execute(double a, double b) { return a - b; } } public class Test { public static void main(String[] args) { MathematicalOperation[] operations = {new Addition(), new Subtraction()}; double numberA = 10; double numberB = 5; for (MathematicalOperation operation : operations) { double result = operation.execute(numberA, numberB); System.out.println("Result: " + result); } } }
Ten kod przedstawia przykład polimorfizmu za pomocą interfejsu i implementujących go klas. Interfejs
MathematicalOperation
definiuje metodę execute
, która przyjmuje dwie liczby i zwraca wynik operacji matematycznej. Klasy Addition
i Subtraction
implementują ten interfejs, dostarczając swoje operacje dodawania i odejmowania. W klasie testowej tworzymy tablicę operacji zawierającą obiekty typów Addition
i Subtraction
, a następnie przy pomocy pętli for
dla każdej operacji wykonywane są obliczenia i wyniki są wyświetlane. To ilustruje, jak różne klasy mogą działać w sposób polimorficzny, wykorzystując wspólny interfejs do różnych funkcjonalności.@Override w koncepcie polimorfizmu
Polimorfizm zakłada, że przesłanianie metod należy wykonywać pamiętając o 2 głowych zasadach:
1. Argumenty muszą być takie same, a typy zwracane muszą być kompatybilne
Klasa nadrzędna (np.
Animal
) określa, w jaki sposób klasa podrzędna (np. Dog
) może korzystać z jej metody. Cokolwiek metoda klasy Animal
przyjmuje jako argument, to ta sama metoda przesłonieta w klasie podrzędnej Dog
musi przyjąć argument tego samego typu.Typ zwracany tej przesłoniętej metody w klasie
Dog
musi być kompatybilny, tzn. zdeklarowany jako Animal
, albo jako typ klasy podrzędnej, czyli Dog
. Obiekt klasy podrzędnej może korzystać z wszystkich metod i zmiennych instancji, które zdeklarowane są w jego klasie nadrzędnej, więc bezpiecznie jest zwrócić podrzędny typ tam, gdzie oczekiwany jest typ klasy nadrzędnej.2. Metoda nie może mieć niższego poziomu dostępu
Zdeklarowany poziom dostępu metody musi być taki sam lub mniej “restrykcyjny”. Nie można przesłonić publicznej metody i zmienić jej w prywatną. Kompilator Java patrzy w pierwszej kolejności zawsze na odwłołanie, więc w takim wypadku byłby trochę zmieszany. Zaczałby najpierw wykonywać metodę publiczną, która nagle w trakcie programu stałaby się prywatna
#mindfuck
.class Animal { public Animal createOffSpring() { return new Animal(); } public void makeSound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override public Dog createOffSpring() { // Przesłanianie metody createOffspring return new Dog(); // Zwracanie podrzędnego typu } @Override public void makeSound() { // Przesłanianie metody makeSound System.out.println("Dog barks"); } } public class TestAnimal { public static void main(String[] args) { Animal animal = new Dog(); Animal offspring = animal.createOffSpring(); // Wywołanie przesłoniętej metody offspring.makeSound(); // Wywołanie przesłoniętej metody } }
W tym przykładzie mamy klasę
Animal
, która posiada dwie metody: createOffSpring()
i makeSound()
. Klasa Dog
dziedziczy po klasie Animal
i przesłania obie te metody, zachowując 2 zasady opisane powyżej. Metoda createOffspring()
w klasie Dog
zwraca obiekt typu Dog
, który jest podrzędnym typem w porównaniu do typu zwracanego przez klasę Animal
. Metoda makeSound()
jest przesłonięta, aby dostosować zachowanie do klasy Dog
.W funkcji
TestAnimal
, tworzymy obiekt klasy Dog
, ale przypisujemy go do zmiennej referencyjnej typu Animal
. Mimo to możemy wywołać przesłonięte metody createOffspring()
i makeSound()
, niezależnie od typu obiektu.Słownik
- Polimorfizm - koncept odnoszący się do możliwości wykorzystania jednej
abstrakcyjnej klasy
lubinterfejsu
do tworzenia wielu konkretnych implementacji. Dzięki temu można traktować różne obiekty różnych klas dziedziczących jako obiekty jednego wspólnego typu.
- Dziedziczenie - mechanizm, w którym klasy podrzedne (
subclass
) mogą dziedziczyć pola i metody od klasy nadrzędnej (superclass
). Dzięki dziedziczeniu, klasy mogą rozszerzać i ponownie wykorzystywać kod, co prowadzi do hierarchicznej struktury programu
Śledź mnie na LinkedIn
Newsletter
Jeśli masz jakieś sugestie lub pytania, proszę napisz do mnie wiadomość: kuba@javampokaze.pl