Typy generyczne w Javie
=======================
Typy generyczne (po angielsku: generics) nazywane są również typami
uogólnionymi, albo po prostu generykami. 

Typy generyczne to mechanizm, który został dodany do Javy stosunkowo późno.
Przy dodawaniu generyków zdecydowano się na zachowanie wstecznej
kompatybilności z wcześniejszymi wersjami Javy, przez co generyki w Javie mają
dużo nienaturalnych ograniczeń.

Inaczej: Generyki są dużo trudniejsze niż wyglądają na pierwszy rzut oka :)

Po co typy generyczne?
Piszemy często klasy, które w zasadzie wyglądają tak samo, różnią się tylko przechowywanym typem, np.
- lista liczb całkowitych, lista napisów i lista obiektów Samochód zachowują
  się tak samo,
- stos liczb całkowitych, stos napisów, stos obiektów Samochód zachowują się
  tak samo,
- kolejka liczb całkowitych, kolejka napisów i kolejka obiektów Samochód
  zachowują się tak samo,
- zbiór liczb całkowitych, zbiór napisów, zbiór obiektów Samochód zachowują się
  tak samo,
- ...

Typy generyczne służą do tego, żeby napisać jeden wzorzec takiej klasy, a
potem go wielokrotnie używać.

Składnia typów generycznych
===========================
Definicję klasy możemy sparametryzować parametrem typowym. Parametr typowy
umieszczamy w nawiasach kątowych po nazwie klasy.

Na przykład jeśli chcemy mieć klasę Koszyk, do której wkładamy elementy tego
samego typu, możemy ją zadeklarować w ten sposób:

public class Koszyk<E> {
...
} 
Parametry typowe z reguły oznaczamy jednoliterowymi identyfikatorami, np. S,
T, K, V, E, itp.

Wewnątrz klasy możemy (w zasadzie) odwoływać się do parametru typowego, jak do
zwykłego typu:
public class Koszyk<E> {
	// możemy zdefinować atrybut
	...
	protected E pierwszyElement;
	...
	// możemy zdefiniować metodę z parametrem typowym
	public void dodaj(E element) {
	...
	}
	// możemy odwołać się do parametru w konstruktorze
	public Koszyk(E pierwszyElement) {
	...
	}
	// możemy użyć parametru jako typu zwracanego:
	public E dajPierwszyElement() {
	...
	}
}

Używanie typów generycznych
===========================
Samo Koszyk<E> to tylko szablon klasy (typu). Ten szablon staje się typem
dopiero, gdy powiemy jakiego typu konkretnego chcemy użyć w miejscu parametru.

Argument typowy podajemy w nawiasach kątowych po nazwie typu, np. typami są:
Koszyk<String>
Koszyk<Integer>

Tworzenie obiektu typu generycznego:
...
Koszyk<String> koszyk = new Koszyk<String>();
...

Od Javy 7 można nie powtarząć typu przy wywołaniu konstruktora, jeśli robimy
proste przypisanie:
Koszyk<String> koszyk = new Koszyk<>();

Klasy opakowujące (wrappery)
============================
Argumentami typowymi do klas generycznych mogą być tylko typy referencyjne, tj. klasy i interfejsy. Nie mogą
być nimi typy pierwotne. 
Na przykład typ Stos<int> NIE jest poprawny. (UWAGA: to jest częsty błąd na egzaminie)

Jeśli chcemy utworzyć klasę generyczną parametryzowaną typem liczb
całkowitych, musimy użyć tzw. klas opakowujących. Każdy typ pierwotny w Javie
ma odpowiadającą mu klasę opakowującą, czyli klasę która typ pierwotny podnosi
do rangi klasy.

Klasy opakowujące dla typów pierwotnych w Javie
int --> Integer (Uwaga: nie Int)
long --> Long
byte --> Byte
short --> Short
float --> Float
double --> Double
char --> Character (Uwaga: nie Char)
boolean --> Boolean

Nowe wersje Javy umieją automatycznie skonwertować z typu pierwotnego na klasę
opakowującą i odwrotnie, czyli takie przypisania będą działać. (Są sytuacje,
kiedy to nie zadziała, ale są dosyć rzadkie.)

int x = new Integer(7); // przypisujemy obiekt klasy Integer na typ pierwotny int
Integer z = 7; // przypisanie typu pierwotnego int do klasy Integer

Uwaga: automatyczna konwersja może prowadzić do nieoczywistych błędów, np.
Integer a = null;
int b = a; // będzie NullPointerException w czymś co wygląda na zwykłe przypisanie

Uwaga: zmienna typu Boolean (pisany wielką literą) może mieć trzy wartości:
true, false i null.
Typ pierwotny boolean może mieć tylko dwie wartości: true i false.

Parametryzowanie kilkoma parametrami
====================================
Klasy mogą być sparametryzowane więcej niż jednym parametrem:
np. 
public class Para<T,S> {
}

Przy użyciu takiego typu podajemy dwa argumenty typowe:
Para<String, Integer> p1 = new Para<>();
Para<String, String> p2 = new Para<>();

Typy generyczne a dziedziczenie
===============================
Dwie instancje typu generycznego to zawsze są różne typy, np. Koszyk<String> i
Koszyk<Integer> to różne typy, nie ma między nimi żadnych związków
dziedziczenia.
Nie ma także związków dziedziczenia nawet jeśli argumenty typowe są w relacji
dziedziczenia, np. String dziedziczy po Object, ale Koszyk<String> nie
dziedziczy po Koszyk<Integer>.

W deklaracji klasy generycznej możemy normalnie używać dziedziczenia, np.
public class Koszyk<E> extends ObiektWiklinowy {
}

// można dziedziczyć po klasie generycznej:
public class Koszyk<E> extends Collection<E> {
}

Ograniczenia: extends, super
============================
Definiując klasę generyczną możemy powiedzieć, że można ją tworzyć tylko z
podklasami danego typu:

public class Koszyk<E extends Owoc> // możemy tworzyć tylko koszyki owoców:

Koszyk<Owoc> // OK
Koszyk<Banan> // OK
Koszyk<Bułka> // ŹLE, Bułka nie jest Owocem

Jako ograniczenie można podać także interfejs:
public class Koszyk<E extends Comparable<E>> // możemy tworzyć tylko koszyki obiektów implementujących interfejs Comparable

Możemy podać kilka ograniczeń:
public class Koszyk<E extends Owoc & Comparable<E>> // możemy tworzyć tylko koszyki owoców implementujących interfejs Comparable

Tylko jedno ograniczenie może być klasą.

Wycieranie typów
================
Uwaga: parametry typowe są tylko dekoracjami w kodzie źródłowym, widoczne są
tylko w czasie kompilacji. W czasie kompilacji są wycierane i w czasie
wykonania nie są dostępne.

Taka klasa generyczna:
public class Para<S,T> {
	private S first;
	private T second;

	public Para(S f, T s){
		this.first = f;
		this.second = s;
	}
	
	public S getFirst() {
		return first;
	}
	public T getSecond() {
		return second;
	}
}

w czasie wykonania wygląda tak:

public class Para {
	private Object first;
	private Object second;

	public Para(Object f, Object s){
		this.first = f;
		this.second = s;
	}
	
	public Object getFirst() {
		return first;
	}
	public Object getSecond() {
		return second;
	}
}


Najważniejsze konsekwencje wycierania typów:
- nie można tworzyć obiektów parametrycznych:
new T(); // nie wiadomo, czy jest taki konstruktor
- nie można tworzyć tablic typu parametycznego
new T[10]; // nie skompiluje się