Jason. Cała informatyka w jednym miejscu!

Wczoraj poruszyłem kwestią związaną z sortowaniem kolekcji. Nie możemy iść dalej z kontynuacją tego tematu bez podstawowego rozumienia typów generycznych. Typy generyczne w języku Java stanowią kręgosłup wszystkich wykorzystywanych kolekcji (i to nie tylko w tym języku!). Zrobię teraz do tego wstęp, aby artykuł jak każdy inny, spełniał swoją rolę jak najlepiej.

TYPY GENERYCZNE W JĘZYKU JAVA W OGÓLE I W SZCZEGÓLE

Termin określany jest wielorako: typ generyczny, typ ogólny, typ uogólniony, "generic", natomiast wszystko sprowadza się do tego samego. Jest to typ nieposiadający żadnego uszczegółowionego fragmentu, który sugerowałby jakikolwiek związek z jakąś klasą, strukturą czy nawet samym zastosowaniem. Przy dziedziczeniu i klasie abstrakcyjnej to skonkretyzowanie, choć częściowe, to zachodzi. Dane składowe mogą sugerować na przykład stan zdrowia zwierzęcia, a metody mogą wskazywać na zachowanie przycisku. Tutaj taka sytuacja nie zachodzi. Typy generyczne zostały wprowadzone w wersji 5.0 Javy i warto je opanować z kilku powodów:

  • podnoszą bezpieczeństwo typów
  • znacznie redukują powtarzalność kodu
  • mimo ich "ogólności" można wymuszać od jakiej klasy zmienna typu generycznego musi pochodzić (np. klasa musi dziedziczyć od jakiejś konkretnie podanej albo musi implementować konkretny interfejs - taką sytuację możecie zobaczyć w artykule o "extends")

Przede wszystkim, uzmysłowić sobie jedno: jeśli pada hasło "typy generyczne", to mamy to rozumieć jako "typy uniwersalne". Nie ma w nim niczego konkretnego, co mogłoby być dostosowane pod jakąkolwiek strukturę czy "rodzinę" klas. Niemniej jednak jesteśmy w stanie ograniczać dopuszczanie zmiennych do takich klas generycznych wymuszając, aby "wpuszczane" były tylko te, które spełniają warunek dziedziczenia od klasy X czy też implementowały interfejs Y.

Kilka słów teraz o rozpoznaniu czegoś takiego w kodzie. Typy generyczne rozpoznacie natychmiastowo po nawiasach kątowych (<>) oraz pojedynczej dużej literce. Najczęściej spotykane, to T oraz E, aczkolwiek mogą być jeszcze jakieś inne. Standardowa konwencja nazewnicza nakazuje, aby domyślnie trzymać się litery T (od "type"), czasami E (od "element") jeśli programujemy kolekcję. Jeśli pojawia się więcej zmiennych, to idziemy dalej z alfabetem, czyli U, V itd. Najprostsza forma może wyglądać tak:

public class GenericExample<T> {
	private final T instance;

	public GenericExample(T instance) {
		this.instance = instance;
	}
	
	public T getInstance() {
		return instance;
	}
}

Kiedy w klasie generycznej pojawia się zmienna, to przy tworzeniu instancji możemy podstawić DOWOLNY obiekt, który spełnia ściśle określone wcześniej warunki. To oznacza, że gdy później będziemy odwoływać się do niej za pomocą gettera, to typ generyczny natychmiast "wciela się" w skonkretyzowany obiekt. Innymi słowy, gdy zmienna już "wie" jaką ma przyjąć postać, to wówczas "transformuje się" ukrycie w typ ściśle określony. Dla przykładu, gdy mając powyższą definicję klasy generycznej podstawiamy do niej "JPanel", to wówczas instancja klasy dostosowuje się "za plecami" i formułuje w taki sposób:

public class GenericExample<JPanel> {
	private final JPanel instance;

	public GenericExample(JPanel instance) {
		this.instance = instance;
	}
	
	public JPanel getInstance() {
		return instance;
	}
}

Dzięki temu, typy generyczne się konkretyzują pod podstawiony obiekt i możemy się potem odwoływać do danych składowych i metod klasy "JPanel". Na finał zaprezentuję przykład jak wygląda tworzenie instancji w oparciu o typ generyczny i gdzie widać zjawisko tej transformacji:

GenericExample<Float> genericFloat = new GenericExample<>(5.42f);
GenericExample<JButton> genericButton = new GenericExample<>(new JButton());

System.out.println(genericFloat.getInstance().isNaN());
System.out.println(genericButton.getInstance().getFont());

Zobaczcie jak wygląda instrukcja tworzenia instancji. Piszemy nazwę klasy, a zaraz po tym otwieramy nawiasy trójkątne i wewnątrz nich podajemy KONKRETNY TYP instancji, jaki nas interesuje. Kiedy podstawimy "Float", Java będzie od nas oczekiwać podania liczby zmiennoprzecinkowej i taką będzie potem reprezentować (przykład wywołania "isNaN"). Jeśli podstawimy "JButton", to dostajemy się do publicznych metod klasy "JButton" (przykład wywołania "getFont") i tak dalej.

To jest BARDZO DUŻE uproszczenie całego działania, więc nie bierzcie sobie tego do serca w 100%, ale tak to prostymi słowy działa.

Typy generyczne w języku Java

Typy ogólne pozwalają na "podstawianie" obiektów dowolnego typu spełniającego określone w definicji warunki (jeśli zostały określone), które wówczas "transformują się" na typ skonkretyzowany.


Wystarczy jak na lekkie wprowadzenie. Spodziewajcie się kolejnych kilku części na temat tego samego, gdyż typy generyczne są naprawdę rozległe, zarówno pod kątem merytorycznym, jak i funkcjonalnym. Tu jest druga część.

PODOBNE ARTYKUŁY