Sobes.tech
Middle

Jak pracować z generycznymi typami w Kotlin?

sobes.tech AI

Odpowiedź od AI

Typy generyczne w Kotlinie pozwalają na tworzenie klas, interfejsów i funkcji, które działają z danymi różnych typów, zachowując bezpieczeństwo typów podczas kompilacji.

Podstawowe pojęcia i metody pracy:

  • Deklaracja klas i interfejsów generycznych:
    class Box<T>(val item: T) // Klasa generyczna z parametrem typu T
    interface MyList<E> { // Interfejs generyczny z parametrem typu E
        fun add(element: E)
        fun get(index: Int): E
    }
    
  • Deklaracja funkcji generycznych:
    fun <T> printAnything(value: T) { // Funkcja generyczna z parametrem typu T
        println(value)
    }
    
  • Tworzenie instancji typów generycznych:
    val intBox: Box<Int> = Box(123) // Określenie konkretnego typu (Int)
    val stringBox = Box("hello") // Kompilator wywnioskuje typ (String)
    
  • Warianty: Pozwalają określić, jak działają podtypy z typami generycznymi.
    • Kowariancja (out): Pozwala na używanie typu i jego podtypów. Oznacza się out. Przykład: List<out Any> może zawierać List<String>. Używane dla typów, które generują wartości (np. odczyt z kolekcji).
      interface Producer<out T> { // out T - kowariantny parametr
           fun produce(): T
      }
      
    • Kontrawariancja (in): Pozwala na używanie typu i jego supertypów. Oznacza się in. Przykład: Comparator<in String> może działać z Comparator<Any>. Używane dla typów, które konsumują wartości (np. zapis do kolekcji).
      interface Consumer<in T> { // in T - kontrawariantny parametr
          fun consume(value: T)
      }
      
    • Inwariancja: Typ w parametrze generycznym musi być dokładnie taki sam. To domyślne zachowanie.
  • Ograniczenia typów (where): Pozwalają ograniczyć typ parametru tak, aby implementował określone interfejsy lub dziedziczył po określonej klasie.
    fun <T : Comparable<T>> max(a: T, b: T): T { // T musi implementować Comparable
        return if (a > b) a else b
    }
    
    fun <T> process(item: T) where T : Runnable, T : Comparable<T> {
        item.run()
        // ... użycie porównywalności
    }
    
  • Projekcja gwiazdkowa (*): Używana, gdy nie zależy nam na konkretnych argumentach typu. Podobnie jak <?> w Javie.
    fun printList(list: List<*>) { // Może przyjąć listę dowolnego typu
         for (item in list) {
             println(item)
         }
    }
    

Typy generyczne w Kotlinie korzystają z wymazywania typów (type erasure), podobnie jak w Javie. Informacje o konkretnych typach tracą się podczas działania programu. Aby pracować z typami w czasie działania, używa się typów reifikowanych.

  • Typy reifikowane (reified): Dostępne tylko dla funkcji inline. Pozwalają zachować informacje o typie parametru w czasie działania.
    inline fun <reified T> checkType(value: Any) { // T - typ reifikowany
        if (value is T) {
            println("Wartość jest typu T")
        } else {
            println("Wartość nie jest typu T")
        }
    }