Na początek wyjaśnijmy tytuł tego rozdziału. Parametr typu to taki parametr, który pozwala podstawić pod niego dowolny typ,
na przykład
String,
Integer,
Item,
List etc. Skoro mówimy o parametrze dla klasy, to musi istnieć miejsce, gdzie ten parametr będziemy umieszczać.
Należy zapamiętać, że miejsce to znajduje się zaraz za nazwą klasy i jest ograniczone nawiasami ostrymi.
W Javie mamy całe mnóstwo przykładów jak taka konstrukcja wygląda. Weźmy na przykład klasę
ArrayList:
Jej definicja jest bardzo rozbudowana, ale nas interesuje
parametr typu.
Zgodnie z tym, co napisaliśmy, jest on zdefiniowany między nawiasami ostrymi. Pytanie, które się Wam zapewne nasuwa, dotyczy
tego, dlaczego jest to akurat litera
E i to w dodatku duże
E.
Odpowiedź tkwi w pewnych umownych zasadach wprowadzonych przez programistów Javy.
Przyjęli oni, że aby odróżnić parametr typu od innych parametrów lub zmiennych, będzie on zawsze pisany dużą literą.
Nie znaczy to, że mała litera się nam nie skompiluje, ale jest to zasada odgórnie narzucona, w dodatku powszechnie stosowana i
dlatego powinniśmy jej używać. Parametr typu ma się wyróżniać.
Sam wybór litery
E
oznacza, że typ, który tutaj zastosujemy, będzie traktowany jako typ elementu listy, a lista może zawierać wiele takich elementów.
Od razu podkreślmy, że nie jest to sztywna reguła i w praktyce często stosujemy inne konwencje.
Dopuszcza się nawet zastosowanie całego słowa (również złożonego z wielkich liter). Czyli w przypadku listy mogłoby być napisane na przykład
ELEMENT.
Własna klasa z parametrem typu
No dobrze, przejdźmy teraz do konkretów. Stwórzmy prostą klasę, której zadaniem będzie przechowywanie danych oraz udostępnianie
informacji o tych danych. Nazwiemy ją
SmartContainer i w pierwszej wersji nie będziemy
definiować własnego parametru typu:
Nasz kontener na dane (nazwijmy je elementami) opiera się na liście, w której te dane są przechowywane.
Zgodnie z tym, co pokazaliśmy w poprzednim rozdziale
Typy generyczne w Javie,
lista ta powinna mieć określony typ, stąd zakładamy od razu, że będziemy przechowywać w niej obiekty typu
String.
Tradycyjnie napiszemy od razu kawałek kodu klasy
Start, tak aby można było uruchomić
cały przykład:
W ten sposób uzyskaliśmy całkiem zgrabny, choć bardzo prosty, kontener na dane. Na zewnątrz mamy wystawione metody takie jak
add, update czy
printAll
i to właśnie z nich korzystamy, uruchamiając kod. W środku, oprócz tego, że nasze metody operują na liście, to jeszcze
drukują informacje na konsolę o tym, co się dzieje w trakcie uruchamiania programu.
Taki kontenerek to całkiem przyjemny kawałek kodu, ale jest z nim jeden problem. Otóż mimo tego, że nie robimy tam niczego specyficznego
dla obiektów typu
String, to on tak naprawdę będzie działał tylko dla obiektów tego typu.
Trochę szkoda, bo klasa
SmartContainer ma potencjał do tego, by być używana częściej i to dla obiektów
różnych typów. Możemy na przykład chcieć wykorzystać taką klasę do przechowywania liczb albo nawet obiektów stworzonej przez nas klasy.
To, co powinniśmy teraz zrobić, to doprowadzić do przebudowania klasy na bardziej ogólną. Nie chcemy oczywiście w ogóle zmieniać logiki działania klasy.
Powinniśmy zapewnić tę samą funkcjonalność, ale działającą na obiektach dowolnego typu. Musimy określić parametr, pod który będziemy mogli podstawić dowolny typ obiektu.
Innymi słowy, musimy dodać do naszej klasy parametr typu. Zmieniamy co trzeba i teraz nasza klasa wygląda tak:
Co zmieniliśmy? Dodaliśmy
parametr, a później użyliśmy go zamiast konkretnego typu we
wszystkich wymaganych miejscach w kodzie.
Zamieniliśmy wszystkie wystąpienia typu
String na zadeklarowany przez nas parametr
T.
Dzięki temu, możemy definiować typ podczas tworzenia obiektu klasy, a więc każdy tworzony obiekt klasy
SmartContainer
może dla nas pracować z innym typem. Najpierw zobaczmy, jak to będzie wyglądało dla klasy
String:
A teraz przygotujmy kawałek programu, który będzie przechowywał w naszym kontenerze liczby typu
Integer (i to bez żadnej zmiany w kodzie kontenera!):
Trzeba przyznać, że takie programowanie jest bardzo efektowne, a także bardzo praktyczne.
Tak naprawdę na parametrach typu opiera się cała obecna Java, szczególnie Java w wersji 8. Bez tej wiedzy ani rusz.
Inny przykład z klasą
SmartContainer pokazujemy w naszym
Kursie Javy 8 do 14
(w rozdziale dotyczącym metody
map i
flatMap).
Interfejs z parametrem typu
Co prawda tytuł tego rozdziału brzmi "Klasa z parametrem typu", ale naturalnym pytaniem, które się nasuwa w tym momencie, jest
to, czy interfejsy też można definiować generycznie za pomocą parametru typu? Oczywiście można i nawet bardzo często trzeba.
Możemy na przykład stworzyć interfejs, który będzie deklarował metody dla naszej klasy
SmartContainer:
Przygotowanie parametru typu wygląda analogicznie, jak w przypadku klas. Pewna zmiana następuje w momencie implementacji interfejsu:
Podczas implementowania interfejsu powinniśmy przekazać do niego wartość parametru (podobnie jak przekazujemy do interfejsu
List).
Nie możemy tutaj podać oczywiście ani typu
String,
ani
Integer, ponieważ nasz kontener działa dla różnych typów. To, co powinniśmy zrobić,
to przekazać
E w nawiasach ostrych. Wtedy typ określony przy tworzeniu obiektu
SmartContainer
będzie przekazany przez parametr
E do klasy, a następnie dalej jako wartość do interfejsu implementowanego
przez tę klasę (a także do interfejsu listy). Wówczas kompilator wie, że klasa i interfejs działają na tym samym typie i wszystko między nimi się zgadza.
Podobnie wygląda to zagadnienie w przypadku dziedziczenia klas, ale to już jest temat na inny rozdział, w innym czasie.
Poruszymy wtedy jeszcze kilka dodatkowych, bardziej złożonych kwestii.
Różne formy parametrów typu
Na koniec jeszcze krótka uwaga na temat form stosowanych podczas definiowania parametrów typu. O jednej konwencji już wspomnieliśmy.
Dotyczyła ona elementu listy, zbioru itp. Takie byty określamy zwykle literą
E.
Znanych jest jeszcze kilka innych konwencji:
- K - od słowa Key, czyli klucz, stosowane na przykład dla określenia typu dla kluczy w mapie
- V - od słowa Value, czyli wartość, stosowane na przykład dla określenia typu dla wartości w mapie
- N - od słowa Number, dla określenia typu liczbowego
- T - od słowa Type, dla określenia dowolnego typu, który nie jest żadnym z pozostałych, bardzo często stosowana litera
- S,U,V itd. - kolejne litery oznaczające kolejne typy stosowane w danym obszarze kodu (tak zgadza się, w nawiasach ostrych możemy określić więcej niż jeden parametr)
Autor: Jarek Klimas
Data: 03 stycznia 2024
Labele: Backend, Podstawowy, Java
Czy informacje, które otrzymałeś, były pomocne?
Jeśli tak, zapraszam Cię do podarowania mi kawy.
Masz pytanie dotyczące prezentowanego materiału?
Coś jest dla Ciebie niejasne i Twoje wątpliwości przeszkadzają Ci w pełnym zrozumieniu treści?
Napisz do nas maila, a my chętnie znajdziemy odpowiednie rozwiązanie.
Najciekawsze pytania wraz z odpowiedziami będziemy publikować pod rozdziałem.
Nie czekaj. Naucz się programować jeszcze lepiej.