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 stanowią kręgosłup wszystkich wykorzystywanych kolekcji w Javie i nie tylko w tym języku. Zrobię teraz do tego wstęp na taki sam kształt jak wtedy z wątkami.

TYPY GENERYCZNE. 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.

Zostały one 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)

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 (więcej informacji znajdziecie tutaj). 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". Jeśli podstawimy "JButton", to dostajemy się do publicznych metod klasy "JButton" 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 działa.

Typy generyczne w Javie

Typy ogólne pozwalają na "podstawianie" obiektów dowolnego typu spełniającego określone w definicji warunki (jeśli w ogóle), 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ęść.