Niniejszy artykuł będzie robił za podsumowanie tego "sznurka" poprzednich wpisów na temat samego dziedziczenia w języku JavaPolimorfizm w języku Java to jeden z fundamentów programowania obiektowego, a w tym materiale skoncentrujemy się na pewnych przykładach, które będą bić polimorfizmem po oczach. W ten sposób zobaczycie jak bardzo potężną bronią dysponujecie!

POLIMORFIZM W JĘZYKU JAVA. ZASTRZYK TEORII

Na początek, wyjaśnienie terminu: polimorfizm zwany też "wielopostaciowością" to postulat programowania obiektowego mówiący o tym, że dane wyrażenie, obiekt lub podprogram może być skonstruowane i wykorzystane na kilka różnych sposobów. Innymi słowy, jedna rzecz może przyjmować wiele wcieleń naraz. Przykładem z życia wziętym może być dorastający chłopiec. Jest czyimś synem czyli ma rodziców, jest uczniem bo chodzi do szkoły, a wsiadając do samochodu jest pasażerem. Jedna osoba przyjmuje wiele ról.

Tak naprawdę, mogliście być już wcześniej świadkami wykorzystania polimorfizmu, tylko nie byliście tego nawet świadomi. Co robiliśmy z metodami klasy "Example"? Przeciążaliśmy je! przeciążanie metod to przejaw stosowania wielopostaciowości. A metoda "toString" przyjmująca na klatę odpowiedzialność formatowania tekstu w chwili podania samej referencji do obiektu? Przesłanialiśmy ją i dostosowywaliśmy tekst do naszych upodobań. Nadpisywanie metod to także zbawienny wpływ polimorfizmu. Metody mogące podlegać przesłonięciu nazywane są często metodami "wirtualnymi".

Być może to dziwne, że nawiązuję do poprzednich wpisów, aczkolwiek chciałem to najpierw "rozbić" na drobniejsze zagadnienia, a dopiero potem "ujawnić prawdę" co to naprawdę jest i jak to opisać własnymi słowami, jak przy pokazywaniu palcem. Poznaliśmy słowa "extends", "abstract", "final", "super", a wcześniej jeszcze adnotację "@Override". Gdybym teraz wstawił ten cały zestaw słówek do jednego akapitu, cała seria artykułów na nic by się zdała. Mając nadzieję że to rozumiecie, przejdźmy do przykładu w postaci kodu źródłowego ukazującego polimorfizm już w całej okazałości.

PRZYKŁADY KODÓW ŹRÓDŁOWYCH

Żeby nic się nikomu nie pokiełbasiło, zademonstruję kilka przykładów razem z tymi, które zostały przedstawione już wcześniej. Zaczniemy od tych najbardziej oczywistych.

PRZECIĄŻANIE METODY

Podpunkt pierwszy to przeciążanie metody, czyli zdefiniowanie drugiej osobnej metody noszącej tę samą nazwę, która ma obowiązek się różnić listą parametrów oraz możliwość zwracania innego typu danych (tutaj rygoru nie ma).

public class Overloading
{
	public float centimetresToInches(float cm)
	{
		return cm / 2.54f;
	}
	
	public double centimetresToInches(double cm)
	{
		return cm / 2.54;
	}
}

PRZESŁANIANIE METODY

Drugi podpunkt to jest podobne hasło, ale znaczące coś zupełnie innego. Polimorfizm w języku Java przejawia się także podczas przesłaniania metody! To jest implementacja w klasie potomnej metody klasy bazowej zawierającej identyczną sygnaturę, która jest wykonywana celem przesłonięcia instrukcji domyślnych.

public class BaseClass
{
	public void doSomething()
	{
		System.out.println("Wykonuję domyślne instrukcje klasy bazowej.");
	}
}

public class ChildClass
{
	@Override
	public void doSomething()
	{
		System.out.println("Wykonuję własne instrukcje klasy potomnej.");
	}
}

W połączeniu ze słowem kluczowym "super", możecie połączyć jedno z drugim. To tak na marginesie o tym przypominam.

PODSTAWIANIE REFERENCJI KLASY POTOMNEJ W MIEJSCE TYPU KLASY BAZOWEJ

Kolejnym polimorficznym przykładem jest możliwość przypisania obiektu potomnego do obiektu typu nadrzędnego:

Animal wolf = new Wolf();

Dużo częściej będziecie to widzieć w miejscu przekazywania parametru typu "klasa potomna" w miejsce typu "klasa bazowa" np. parametrem jest klasa "JPanel", a Wy przekazujecie instancję klasy potomnej klasy "JPanel". Kiedy to przejdzie, a kiedy nie? Kiedy metoda wykorzystująca typ "bardziej bazowy" korzysta wyłącznie z metod należących do klasy bazowej. Czyli wywołanie metody z klasy "JPanel" będzie OK, ale wywołanie metody z Waszej klasy potomnej już będzie wymagało modyfikacji.

Jeszcze co jest bardzo ważne to to, że nie dochodzi do utraty referencji i kompilator zachowuje odwołanie do klasy potomnej czyli "wie" jakimi "wersjami" metod ma się posługiwać, gdyby jakaś została przesłonięta. To jest przejaw tak zwanego "wiązania dynamicznego" i objawia się traktowaniem danej instancji jak typ wynikający z referencji, a nie z typu deklarowanego.

WYWOŁYWANIE METODY Z KOLEKCJI TYPU INTERFEJSOWEGO

Wykorzystanie interfejsu jako części wspólnej tablicy lub kolekcji zamiast klasy. To także jest przejaw polimorfizmu, gdyż na mocy wymuszanego protokołu implementacji treści abstrakcyjnej metody interfejsu, kompilator ma pewność że każda z instancji będzie dysponować własną wersją bloku instrukcji. Możemy to wykorzystać do obsługi tablicy / kolekcji przez pętlę rozszerzoną:

public interface Recoverable
{
	void recoverSelf();
}

public class Main
{
	public static void main(String[] args)
	{
		ArrayList<Recoverable> recoverables = new ArrayList<>();

		for (Recoverable recoverable : recoverables)
		{
			recoverable.recoverSelf();
		}
	}
}

W ten sposób nigdy, NIGDY nie będzie sytuacji, w której do kolekcji lub tablicy zostanie wpuszczona instancja niedysponująca metodą interfejsu!


OK, na tym kończymy! Dziękuję wszystkim tym, którzy wytrwali do końca i zapoznali się z każdym przykładem bez wyjątku. To chyba najważniejsze przykłady jakie udało mi się opracować na bazie kilkuletnich spostrzeżeń.

PODOBNE ARTYKUŁY