Kotlin Academy · Lektion

Varians vid deklaration: in och out

Använd kovarianta (out) och kontravarianta (in) typparametrar korrekt.

Lektion 2 av 413 steg

Varians vid deklaration: in och out är en gratis lektion i Kotlin Academy på CoddyKit. Detta är lektion 2 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Kotlin Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Kotlin Academy innehåller totalt 4 lektioner.

Variansproblemet

I Kotlin är List<String> en subtyp av List<Any> eftersom List deklareras med out T. Utan variansannoteringar skulle detta inte gälla.

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) }

Kovarians med out

out T betyder att klassen bara kan producera T-värden (returnera dem), aldrig konsumera dem. Därför är Producer<Dog> en subtyp av 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>
}

Kontravarians med in

in T betyder att klassen bara kan konsumera T-värden (ta emot dem som parametrar), aldrig producera dem. Därför är Consumer<Animal> en subtyp av 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>
}

Begränsning för out: ingen in-position

Med out T får T inte förekomma i parameterposition (in-position). Kompilatorn framtvingar detta.

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())
}

Begränsning för in: ingen out-position

Med in T får T inte förekomma i returposition (out-position).

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")
}

Exempel från Kotlins standardbibliotek

Comparable<in T> är kontravariant: en Comparable<Number> kan jämföra alla subtyper av Number. List<out T> är kovariant: en lista med katter är en lista med djur.

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)
}

Invarians: MutableList

MutableList<T> är invariant: den kan både producera och konsumera T, så det finns ingen subtypsrelation mellan olika T-typer.

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)
}

Varians vid användningsstället som alternativ

När du inte kan ändra klassen använder du varians vid användningsstället: out på anropsstället för att projicera en typ till kovarians.

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)
}

Praktiskt: Repository-mönstret

Använd kovarians för skrivskyddade repositoryn och kontravarians för skrivbara mottagare för att modellera ett tydligt dataflöde.

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>

Guide för val av varians

Välj varians utifrån hur T används: out = producent (returnerar endast T), in = konsument (accepterar endast T), invariant = båda (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)
}

Invariant klass med out-funktion

Även i en invariant klass kan du använda out vid användningsstället för specifika funktionsparametrar.

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)
}

Snabbkontroll

Vad betyder out T på en typparameter?

Sammanfattning

out T (kovarians) tillåter subtypning när värden produceras. in T (kontravarians) tillåter subtypning när värden konsumeras. Invarian­ta typer (som både läser och skriver) har ingen subtypsrelation mellan olika T.

Gratis att börja

Lär dig Kotlin med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
51
Lektioner
203

Vanliga frågor

Är lektionen ”Varians vid deklaration: in och out” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Kotlin Academy, inklusive ”Varians vid deklaration: in och out”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Kotlin Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Varians vid deklaration: in och out”?

Använd kovarianta (out) och kontravarianta (in) typparametrar korrekt. Ni övar på Kotlin Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Kotlin Academy?

Du behöver inga förkunskaper. Utbildningen i Kotlin Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.

Hur lång tid tar lektionen ”Varians vid deklaration: in och out”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Kotlin Academy-lektionen?

Ja. Varje Kotlin Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Generiska funktioner och typbegränsningar med where
  2. Varians vid deklaration: in och out
  3. Stjärnprojektion och när * ska användas
  4. Type erasure och reified-typparametrar
← Tillbaka till Kotlin Academy