0Pricing
Kotlin Academy · Lekcja

Wariancja deklarowana w miejscu deklaracji: in i out

Poprawnie używaj kowariantnych (out) i kontrawariantnych (in) parametrów typów.

Wariancja deklarowana w miejscu deklaracji: in i out 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

W Kotlinie List<String> jest podtypem List<Any>, ponieważ List jest zadeklarowany z użyciem out T. Bez adnotacji wariancji taka relacja by nie zachodziła.

val strings: List<String> = listOf("a", "b")
val anys: List<Any> = strings  // OK because List<out T>
// MutableList<String> is NOT a subtype of MutableList<Any>:
// val m: MutableList<Any> = mutableListOf("x") // compile error
fun main() { println(anys) }

Kowariancja z out

out T oznacza, że klasa może jedynie tworzyć wartości typu T (zwracać je), ale nigdy ich przyjmować. Dzięki temu Producer<Dog> jest podtypem Producer<Animal>.

interface Producer<out T> {
    fun produce(): T
}
class DogProducer : Producer<String> {
    override fun produce() = "Woof!"
}
fun sound(p: Producer<Any>) = println(p.produce())
fun main() {
    val dog = DogProducer()
    sound(dog)  // OK: Producer<String> is a subtype of Producer<Any>
}

Kontrawariancja z in

in T oznacza, że klasa może jedynie przyjmować wartości typu T (akceptować je jako parametry), ale nigdy ich tworzyć. Dzięki temu Consumer<Animal> jest podtypem Consumer<Dog>.

interface Consumer<in T> {
    fun consume(item: T)
}
class Printer : Consumer<Any> {
    override fun consume(item: Any) = println(item)
}
fun feedDog(consumer: Consumer<String>) = consumer.consume("Dog")
fun main() {
    val printer = Printer()
    feedDog(printer)  // OK: Consumer<Any> is a subtype of Consumer<String>
}

Ograniczenie out: brak pozycji in

W przypadku out T T nie może występować w pozycji parametru (in). Kompilator wymusza przestrzeganie tej zasady.

interface ReadOnly<out T> {
    fun get(): T          // OK: out position
    // fun set(t: T) {}  // Error: T in in-position violates out variance
}
class Box<out T>(private val value: T) : ReadOnly<T> {
    override fun get() = value
}
fun main() {
    val box: ReadOnly<Any> = Box<String>("hello")
    println(box.get())
}

Ograniczenie in: brak pozycji out

W przypadku in T T nie może występować w pozycji typu zwracanego (out).

interface WriteOnly<in T> {
    fun set(t: T)        // OK: in position
    // fun get(): T {}  // Error: T in out-position violates in variance
}
class Sink<in T> : WriteOnly<T> {
    override fun set(t: T) = println("Received: $t")
}
fun main() {
    val sink: WriteOnly<String> = Sink<Any>()
    sink.set("hello")
}

Przykłady z biblioteki standardowej Kotlin

Comparable<in T> jest kontrawariantny: obiekt Comparable<Number> może porównywać dowolny podtyp Number. List<out T> jest kowariantny: lista kotów jest listą zwierząt.

fun sortNumbers(list: List<Number>) = list.sortedWith(compareBy { it.toDouble() })
fun main() {
    val ints: List<Int> = listOf(3, 1, 2)
    val sorted = sortNumbers(ints)  // OK: List<Int> is List<Number>
    println(sorted)
}

Niezmienniczość: MutableList

MutableList<T> jest niezmienniczy względem wariancji: może zarówno tworzyć, jak i przyjmować wartości typu T, dlatego między różnymi typami T nie zachodzi relacja podtypów.

fun addNumber(list: MutableList<Number>) { list.add(1.5) }
fun main() {
    val ints = mutableListOf<Int>(1, 2)
    // addNumber(ints) // compile error: MutableList<Int> is not MutableList<Number>
    val nums = mutableListOf<Number>(1, 2)
    addNumber(nums)   // OK
    println(nums)
}

Wariancja w miejscu użycia jako alternatywa

Gdy nie można zmienić klasy, należy użyć wariancji w miejscu użycia: użyj out w miejscu wywołania, aby rzutować typ na kowariantny.

fun copy(from: MutableList<out Any>, to: MutableList<Any>) {
    for (item in from) to.add(item)
}
fun main() {
    val src = mutableListOf("a", "b", "c")
    val dest = mutableListOf<Any>()
    copy(src, dest)
    println(dest)
}

Praktyka: wzorzec repozytorium

Użyj kowariancji dla repozytoriów tylko do odczytu, a kontrawariancji dla miejsc docelowych tylko do zapisu, aby modelować przejrzysty przepływ danych.

interface Repository<out T> {
    fun findAll(): List<T>
    fun findById(id: Int): T?
}
interface Writer<in T> {
    fun save(entity: T)
}
interface ReadWriteRepo<T> : Repository<T>, Writer<T>
// ReadWriteRepo<User> is neither sub nor super of ReadWriteRepo<Admin>

Przewodnik po wyborze wariancji

Wybierz wariancję na podstawie sposobu użycia T: out = producent (tylko zwraca T), in = konsument (tylko przyjmuje T), niezmienniczy = oba zastosowania (MutableList, Channel).

// Quick mental model:
// out T: source/producer  — List, Flow, Sequence
// in  T: sink/consumer    — Comparable, Continuation
// invariant: read+write   — MutableList, Channel, MutableStateFlow
fun main() {
    val nums: List<Number> = listOf(1, 2, 3)  // List is out
    println(nums)
}

Niezmiennicza klasa z funkcją out

Nawet w niezmienniczej klasie można użyć out w miejscu użycia dla określonych parametrów funkcji.

class Stack<T>(private val items: MutableList<T> = mutableListOf()) {
    fun push(item: T) = items.add(item)
    fun pop(): T? = items.removeLastOrNull()
}
fun printAll(stack: Stack<out Any>) {
    // can only read, not write
    println(stack.pop())
}
fun main() {
    val s = Stack<String>()
    s.push("hello")
    printAll(s)
}

Szybkie sprawdzenie

Co oznacza out T przy parametrze typu?

Podsumowanie

out T (kowariancja) umożliwia relację podtypów podczas tworzenia wartości. in T (kontrawariancja) umożliwia relację podtypów podczas przyjmowania wartości. Typy niezmiennicze (zarówno odczytywane, jak i zapisywane) nie mają relacji podtypów dla różnych typów T.

Często zadawane pytania

Czy lekcja „Wariancja deklarowana w miejscu deklaracji: in i out” jest bezpłatna?

Tak — pełny tekst „Wariancja deklarowana w miejscu deklaracji: in i out” 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 deklarowana w miejscu deklaracji: in i out”?

Poprawnie używaj kowariantnych (out) i kontrawariantnych (in) parametrów typów. Ć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 deklarowana w miejscu deklaracji: in i out”?

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 generyczne i ograniczenia typów za pomocą where
  2. Wariancja deklarowana w miejscu deklaracji: in i out
  3. Projekcja gwiazdkowa i kiedy używać *
  4. Wymazywanie typów i parametry typów reified
← Powrót do Kotlin Academy