Site icon Java blog

Java #45: typy generyczne (generics)

java generics

Klasa generyczna i typy generyczne

Używając kolekcji bardzo często korzystałem z magicznych ostrych nawiasów, w obrębie których wpisywałem nazwę klasy, której typu miały być jej elementy. Za przykład może posłużyć ArrayList<String>, która jest strukturą przechowującą wiele różnych obiektów typu String. Taki zapis nazywa się klasą generyczną (ang. generic class). Każda klasa w Javie może być klasą generyczną, jeśli w jej sygnaturze zawrzesz ostre nawiasy a w nich parametr opisujący typ (może być ich więcej niż jeden). Dzięki używaniu takiej abstrakcji możesz zdefiniować działanie klasy niezależnie od tego, jaki obiekt będzie do niej przesyłany. Dzieje się tak dlatego, że typ generyczny (czyli taki, który jest nieustalony na poziomie projektowania klasy) można wykorzystywać do implementacji metod, pól czy konstruktorów.

Spójrz na poniższy przykład.

public class Italian {
  // ciało klasy dla włoskich restauracji
}
public class FastFood {
  // ciało klasy dla fast foodów
}

Zamiast rzutowania

Stworzyłem sobie dwie klasy. Ich zawartość w tej lekcji nie ma znaczenia, bowiem, wiesz już jak implementować pola, konstruktory czy metody. Załóżmy, że każda z nich zawiera menu, nip, adres i właściciela. Dodatkowo dla rozróżnienia włoskie restauracje posiadają dodatkowo możliwość rezerwacji lokalu na specjalne okoliczności a fast foody zakup jedzenia z dowozem do domu. Teraz, posiadając, te dwie różne klasy, które są restauracjami, chciałbym napisać kod jednej klasy, która w zależności od mojego wyboru będzie restauracją włoską albo fast foodem.

W teorii mógłbym to osiągnąć pisząc klasę abstrakcyjną i później rzutując ją w zależności od potrzeb na obiekt typu FastFood lub Italian. W dawnych czasach, kiedy generyki jeszcze nie istniały tak wyglądało programowanie. Jednak ponieważ takie rzutowanie jest bardzo podatne na błędy, wymyślono alternatywę.

public class Restaurant<T, U> {
	T category;
	U id;
	
	public T getCategory() {
		return category;
	}
	public void setCategory(T category) {
		this.category = category;
	}
	public U getId() {
		return id;
	}
	
	public void setId(U id) {
		this.id = id;
	}
}

Teraz klasa Restaurant może przyjąć cechy zarówno restauracji włoskiej jak i fast foodu (i jakiejkolwiek w przyszłości sobie nie zażyczysz). Dodatkowo istnieje jeszcze jeden generyczny parametr U, który oznacza typ Twojego id. Jest to o tyle fajne, że jeśli przekroczysz zakres np. Integer to bardzo łatwo możesz zmienić go np. w Long. Zmieniając typ generyczny, zmieniasz go we wszystkich swoich restauracjach. Proste prawda?

Zasady używania generyków

Jak widzisz, możesz deklarować dowolną ilość typów generycznych w klasie. Nie muszą być to wcale litery T czy U. Prawdę mówiąc, w ogóle to nie muszą być tylko pojedyncze wielkie litery, ale jakakolwiek inna nazwa zgodna z kompilatorem Javy. Konwencja jest jednak taka, aby w przypadku generyków używać samym wielkich liter. Dodatkowo pamiętaj, że typem generycznym może być tylko typ referencyjny. Nie możesz używać do nich typów prostych (np. int). Dotąd typy generyczne często wykorzystywałem w przypadku struktur danych (jak np. lista czy mapa). Np. List<MyClass> list = new ArrayList<MyClass>();

Od Javy w wersji 7 nie musisz już wpisywać nazwy klasy w ostrych nawiasach po prawej stronie znaków 'równa się’. Obecnie wystarczy użyć pustych nawiasów: List<String> list = new ArrayList<>();

Nie ma limitu typów generycznych, które możesz wpisać w ostre nawiasy, jednak podobnie jak z argumentami metod, za rozsądną liczbę przyjmuje się do trzech typów. Pewnym ograniczeniem w używaniu programowania uogólnionego jest używanie tylko zmiennych referencyjnych w generykach. Stąd też zawsze, gdy chcesz używać w nich typów prostych, staraj się zamiast nich wykorzystać klasy opakowujące, o których już pisałem wcześniej*.

W przypadku restauracji użycie ich w kodzie będzie wyglądało tak:

Restaurant<Italian, Long> pizzaNapoli = new Restaurant<>();
Restaurant<FastFood, Long> kfc  = new Restaurant<>();
pizzaNapoli.getCategory().. // dalsze użycie zgodnie z Twoją implementacją

W kolejne lekcji nauczę Cię bardziej zaawansowanych przypadków użycia typów generycznych.

Link do kodu: https://github.com/developeronthego/java-advanced/tree/master/src/main/java/advanced/lesson1

*Przypomnienie, czym jest klasa opakowująca: Java #3: Autoboxing, unboxing i literały

Typy generyczne używane są w kolekcjach, o których mowa: Java #30: kolekcje (ogólnie)

Exit mobile version