0Pricing
Kotlin Academy · Lekcja

Wariancja w miejscu deklaracji

in i out

Wariancja w miejscu deklaracji to bezpłatna lekcja Kotlin Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Kotlin Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Kotlin Academy zawiera 4 lekcji w sumie.

Problem wariancji

Czy List<String> jest rodzajem List<Any>? Czasami tak, a czasami nie. Wariancja opisuje relacje między typami generycznymi, gdy pozostające w nich parametry typu są ze sobą powiązane. Kotlin używa out i in, aby wyrazić te relacje.

Domyślna inwariantność

Domyślnie typy generyczne są inwariantne: Box<String> NIE jest typem Box<Any>, mimo że String jest podtypem Any. Zapobiega to niebezpiecznym operacjom.

class Box<T>(val value: T)

fun main() {
    val strBox = Box("hi")
    // val anyBox: Box<Any> = strBox // would not compile
    println(strBox.value)
}

Kowariancja z out

Oznaczenie parametru typu jako out sprawia, że staje się on kowariantny: Producer<String> staje się podtypem Producer<Any>. Typ może wtedy występować wyłącznie w pozycjach wyjściowych, czyli jako typ zwracany.

class Producer<out T>(private val item: T) {
    fun produce(): T = item
}

fun main() {
    val strProducer: Producer<String> = Producer("hello")
    val anyProducer: Producer<Any> = strProducer
    println(anyProducer.produce())
}

Dlaczego out jest bezpieczne

Jeśli klasa tylko produkuje, czyli zwraca, T i nigdy go nie przyjmuje, traktowanie Producer<String> jako Producer<Any> jest bezpieczne, ponieważ każdy zwracany przez nią String jest również Any.

out w bibliotece standardowej

Przeznaczona tylko do odczytu klasa List<out E> w Kotlinie jest kowariantna. Dlatego List<String> można użyć tam, gdzie oczekiwany jest typ List<Any>.

fun printAll(items: List<Any>) {
    items.forEach { println(it) }
}

fun main() {
    val names: List<String> = listOf("Ann", "Bob")
    printAll(names)
}

Kontrawariancja z in

Oznaczenie parametru typu jako in sprawia, że staje się on kontrawariantny: Consumer<Any> staje się podtypem Consumer<String>. Typ może wtedy występować wyłącznie w pozycjach wejściowych, czyli jako parametr.

class Consumer<in T> {
    fun consume(item: T) {
        println("Consumed $item")
    }
}

fun main() {
    val anyConsumer: Consumer<Any> = Consumer()
    val strConsumer: Consumer<String> = anyConsumer
    strConsumer.consume("hi")
}

Dlaczego in jest bezpieczne

Konsument przyjmujący Any z pewnością może przyjąć String. Dlatego Consumer<Any> może zastąpić Consumer<String> wszędzie tam, gdzie jest on wymagany.

Mnemonik PECS

Należy pamiętać o zasadzie Producer Extends, Consumer Super, czyli w terminologii Kotlina: out dla producentów (dane wyjściowe) i in dla konsumentów (dane wejściowe). Typ, który tylko zwraca T, używa out, a typ, który tylko przyjmuje T, używa in.

Łączenie in i out

Parametr typu używany zarówno jako dane wejściowe, jak i wyjściowe, na przykład w mutowalnej liście, musi pozostać inwariantny. Typy Function pokazują oba przypadki: parametry są oznaczone jako in, a typ zwracany jako out.

fun main() {
    val f: (Number) -> Int = { it.toInt() }
    val g: (Int) -> Number = f
    println(g(3.0.let { Number::class; 5 }))
}

Wariancja w miejscu deklaracji oznacza jedno miejsce

Wariancję w miejscu deklaracji określa się raz, bezpośrednio w klasie, za pomocą in/out. Każde użycie tej klasy automatycznie korzysta z określonej wariancji, w przeciwieństwie do Javy, w której symbole wieloznaczne trzeba powtarzać przy każdym użyciu.

Wybór między in i out

Należy zadać sobie pytanie: czy ta klasa tylko produkuje T — wtedy należy użyć out —, tylko przyjmuje T — wtedy należy użyć in —, czy robi jedno i drugie — wtedy należy pozostawić ją inwariantną? Dopasowanie modyfikatora do roli sprawia, że typy generyczne są elastyczne i bezpieczne.

Szybkie sprawdzenie

Co oznacza modyfikator out?

Podsumowanie

Typy generyczne są domyślnie inwariantne. Należy używać out dla kowariancji, gdy typ tylko produkuje wartości, oraz in dla kontrawariancji, gdy typ tylko przyjmuje wartości. Należy pamiętać: producenci używają out, a konsumenci in. Następnie zostanie omówione stosowanie wariancji w miejscu użycia za pomocą projekcji typów.

Często zadawane pytania

Czy lekcja „Wariancja w miejscu deklaracji” jest bezpłatna?

Tak — pełny tekst „Wariancja w miejscu deklaracji” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Kotlin Academy, przejdź na CoddyKit PRO. Kurs Kotlin Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Wariancja w miejscu deklaracji”?

in i out Ćwiczysz Kotlin Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Kotlin Academy?

Nie wymagamy żadnego doświadczenia. Kotlin Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Wariancja w miejscu deklaracji”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Kotlin Academy?

Tak. Każda lekcja Kotlin Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Funkcje i klasy generyczne
  2. Wariancja w miejscu deklaracji
  3. Wariancja w miejscu użycia
  4. Ograniczenia typów generycznych
← Powrót do Kotlin Academy