programowanieProgramista backendowy

Jak w Kotlinie zrealizowane są generics? Jakie ograniczenia istnieją, jak działa inwariantność, kowariancja i kontrawariancja, i czym różnią się one od dżenerków w Javie? Podaj przykłady użycia.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Generics w Kotlinie pozwalają na tworzenie uniwersalnych i typowo bezpiecznych struktur danych oraz funkcji. Główna cecha: Kotlin realizuje system typów generics na poziomie kompilacji, tak jak Java, ale z bardziej rygorystyczną typizacją i rozszerzoną składnią wariancji (kowariancja i kontrawariancja).

Ograniczenia dżenerków:

  • Typy-parametry domyślnie są inwariantne.
  • Nie można stworzyć instancji typu-parametru (T() jest zabronione).
  • Brak dostępu do informacji o typach generics w czasie wykonywania (type erasure).

Wariancja:

  • Kowariancja (out T): pozwala na używanie podtypów.
  • Kontrawariancja (in T): pozwala na używanie supertypów.
  • Inwariantność: typ bez modyfikatora wariancji.

Przykład kowariancji:

interface Producer<out T> { fun produce(): T }

Przykład kontrawariancji:

interface Consumer<in T> { fun consume(item: T) }

Różnice od Javy:

  • Składnia jest bardziej wyraźna i zwięzła (out zamiast ? extends, in zamiast ? super).
  • Brak typów dzikich (?, tylko in/out).
  • Zakaz tworzenia instancji parametru generics.

Pytanie z podchwytliwością

Pytanie: "Czy można w Kotlinie zadeklarować tablicę tablic (Array<Array<Int>>) jako Array<out Array<Int>> i co się stanie przy próbie zapisania do takiej tablicy?"

Odpowiedź: Tak, można zadeklarować jako Array<out Array<Int>>, ale taka tablica staje się tylko do odczytu (read-only):

val arr: Array<out Array<Int>> = Array(1) { Array(1) { 0 } } arr[0] = arrayOf(1, 2, 3) // Błąd kompilacji!

Próba zapisania wartości doprowadzi do błędu — tablica generyczna z parametrem out nie pozwala na zapis elementów, ponieważ naruszyłoby to bezpieczeństwo typów.

Przykłady rzeczywistych błędów z powodu braku znajomości niuansów tematu


Historia

W zespole próbowano stworzyć tablicę obiektów generics z typem parametru out, a następnie — włożyć do niej wartości przez set(index, value). Kod kompilował się, ale generował błąd w czasie wykonywania, a kilka funkcji okazało się nie działać.


Historia

Pewnego razu podczas migracji biblioteki z Javy do Kotlinu pozostawiono typy dzikie (? extends ...), a w Kotlinie po prostu skopiowano typy bez zmian na out/in. Wynik — kompilacja się nie powiodła, a przy "obchodzie" błąd wystąpił w czasie wykonywania, co skomplikowało proces debugowania.


Historia

Używano wariancji in/out z klasą użytkownika, ale pomylono modyfikatory, deklarując interfejs Stack<in T> zamiast Stack<out T>. Doprowadziło to do niemożności zwracania elementów ze stosu: sygnatura metody naruszała umowę systemową out/in.