programowanieProgramista Backendowy

Opowiedz o kluczowych aspektach pracy z generowaniem i używaniem generyków (generics) w Javie, jakie subtelności są ważne dla bezpiecznego stosowania?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

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:

  • Type Erasure: Na etapie kompilacji informacje o typach parametrów są usuwane, dlatego nie można na przykład utworzyć tablicy typu generycznego: new List<String>[10] — błąd kompilacji.
  • Ograniczenia w używaniu:
    • Nie można tworzyć instancji z parametryzowanego typu: T obj = new T();
    • Nie można używać instanceof z parametryzowanymi typami: if(obj instanceof List<String>) — błąd.
    • Nie można tworzyć statycznych pól parametryzowanych typów.
  • Dzikie karty: ? extends T — kowariancja (czytanie), ? super T — kontrawariancja (zapisywanie).
  • Zasada PECS (Producer Extends, Consumer Super): Jeśli trzeba tylko czytać — używaj extends, jeśli pisać — super.

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 z pułapką.

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

Przykłady rzeczywistych błędów wynikających z braku wiedzy o subtelnościach tematu.


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 tablica Object[], 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.