Typy generyczne

Typy generyczne w Javie pozwalają realizować programowanie uogólnione, i znacznie redukują ilość powtarzającego się kodu. Zostały wprowadzone w Javie 5 i stały się jednym z kluczowych elementów języka. Typowym przykładem ich wykorzystania są kolekcje wchodzące w skład Collections Framework, ale bez problemu można także spotkać ich zastosowanie we frameworkach oraz codziennym kodzie.

Po co nam typy generyczne?

Wyobraź sobie sytuację, w której chcesz przechować w swoim programie kilka obiektów tego samego typu, np. Book. Można do tego wykorzystać tablice i zapisać np.:

Book[] books = new Book[1000];

Tablice mają jednak swoje wady, nie są w stanie zwiększać swojego rozmiaru, gdy zabraknie w nich miejsca, usuwanie elementów jest kłopotliwe. Zamiast tego najczęściej korzystamy więc z kolekcji, np. list, albo zbiorów, które zostały wprowadzone do Javy w wersji 1.2 w postaci Collections Framework. Ze względu na to, że kolekcje powinny być uniwersalne, to pierwotnie operowały one na typie Object, który dawał możliwość wykorzystania polimorfizmu.

Tworząc więc np. obiekt ArrayList w taki sposób:

List books = new ArrayList();

do przechowywania obiektów w ramach kolekcji tworzona była tablica typu Object[]. Ma to oczywiście zaletę w postaci tego, że w tablicy takiej mogą być przechowywane obiekty dowolnego typu, ale ma równie duże wady. Przy pobieraniu obiektów z takiej kolekcji zwracane są referencje typu Object, co wymusza na nas późniejsze rzutowanie. Błędy jest więc zapis:

List books = new ArrayList();
books.add(new Book("Potop", "Henryk Sienkiewicz"));
Object book = books.get(0);
System.out.println(book.getTitle()); //błąd, w typie Object nie ma metody getTitle()

Konieczne jest rzutowanie:

List books = new ArrayList();
books.add(new Book("Potop", "Henryk Sienkiewicz"));
Book book = (Book) books.get(0);
System.out.println(book.getTitle()); //ok, referencja typu Book

To, co moglibyśmy zrobić w takiej sytuacji, to stworzenie własnych klas reprezentujących listy, dedykowane do odpowiednich typów, np. BookList, MagazineList, AuthorList itd. Zdecydowana większość kodu takich klas byłaby jednak powtarzalna, a różniłoby je tylko to, na jakich typach operują.

Typy generyczne

Znanym rozwiązaniem tego problemu jest programowanie uogólnione / generyczne. Problem polegał na tym, że wprowadzenie tego paradygmatu do Javy nie było takie proste, ponieważ od samego początku stawiano w niej na pierwszym miejscu kompatybilność wsteczną. Rozwiązaniem okazały się typy generyczne wprowadzone w Javie 1.5, które reprezentowane są przez ostre nawiasy. Od teraz powyższy kod można było zapisać w sposób, który nie wymaga rzutowania:

List<Book> books = new ArrayList<Book>();
books.add(new Book("Potop", "Henryk Sienkiewicz"));
Book book = books.get(0); //metoda get() zwraca referencję Book, a nie Object
System.out.println(book.getTitle()); //ok, referencja typu Book
Typy generyczne w Javie

W kolejnych wersjach Javy wprowadzano jeszcze pewne usprawnienia. W Javie 1.7 dodano automatyczne wnioskowanie typów (ang. type inference), dzięki czemu nie trzeba było już podawać typu generycznego dwukrotnie, tylko, zamiast tego można wykorzystać operator diamentu:

//przed Javą 7
List<Book> books = new ArrayList<Book>();
//od Javy 7
List<Book> books = new ArrayList<>();

Oczywiście kolekcje są najprostszym przykładem wykorzystania typów generycznych, ale wykraczają one znacząco poza to. Możemy definiować zarówno własne klasy jak i pojedyncze metody generyczne.

Ograniczenia

Typy generyczne wprowadzone w Javie nie są rozwiązaniem idealnym i posiadają istotne ograniczenia. Najważniejsze z nich jest to, że jako typów generycznych możemy używać wyłącznie typów obiektowych. Nie możemy natomiast używać typów prostych. Z tego powodu możemy stworzyć listę, która będzie przechowywała obiekty String:

List<String> words = new ArrayList<>(); //ok

ale nie możemy stworzyć listy do zapisywania wartości int:

List<int> numbers = new ArrayList<>(); //błąd

Z tego powodu wraz z typami generycznymi w Javie 1.5 wprowadzono jednocześnie typy opakowujące i w celu stworzenia listy przechowującej liczby całkowite należy zapisać:

List<Integer> numbers = new ArrayList<>(); //ok

Potencjalna przyszłość

Architekci Javy mocno pracują nad nowymi elementami języka i jedną z takich nowości mają być prymitywne obiekty rozwijane w ramach JEP 401: Primitive Objects. Kolejnym krokiem będzie zunifikowanie typów prostych oraz ich obiektowych odpowiedników w ramach JEP 402: Unify the Basic Primitives with Objects. Ostatecznie będzie to oznaczało, że List<int> i List<Integer> będą sobie równoważne.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.