Generics pozwalają tworzyć klasy, interfejsy i metody z parametrami typów, co zapewnia sprawdzanie typów na etapie kompilacji i pomaga uniknąć ClassCastException.
Kluczowe cechy i pułapki:
new List<String>[10] — błąd kompilacji.T obj = new T();if(obj instanceof List<String>) — błąd.? extends T — kowariancja (czytanie), ? super T — kontrawariancja (zapisywanie).Przykład:
// Kowariancyjne podejście do czytania void printNumbers(List<? extends Number> numbers) { for (Number n : numbers) { System.out.println(n); } } // Kontrawariancyjne podejście do zapisywania void addIntegers(List<? super Integer> list) { list.add(10); }
Pytanie: „Czym się różnią List<Object> i List<?>? Czy można dodać dowolny obiekt do List<?>?”
Odpowiedź: Nie, do List<?> nie można nic dodać (oprócz null), ponieważ kompilator nie wie, jaki dokładnie parametr typu tam jest. Natomiast do List<Object> można dodawać dowolne obiekty.
Przykład:
List<?> list1 = new ArrayList<String>(); // list1.add("test"); // Błąd kompilacji! List<Object> list2 = new ArrayList<>(); list2.add("test"); // OK
Historia
Zespół programistów próbował zrealizować cache na podstawie tablicy z parametryzowanego typu
T[]. Z powodu type erasure i niemożności tworzenia tablic typów generycznych rozwiązanie nie działało jak oczekiwano: powstała tablicaObject[], prowadząca do ClassCastException w czasie uruchomienia.
Historia
W jednym z mikroserwisów programista próbował zrealizować odbiornik, używający List<?> jako parametru, i próbował modyfikować kolekcję. To spowodowało błąd kompilacji i opóźnienia w wydaniu, ponieważ trzeba było refaktoryzować logikę z uwzględnieniem PECS.
Historia
W projekcie integracji z zewnętrznym systemem programista popełnił błąd, nadpisując kolekcję jednego typu innym przez nieprzetworzony raw-type: List list = new ArrayList<String>(), co doprowadziło do ClassCastException i awarii serwisu na produkcji podczas próby rzutowania elementów na inne typy.