0
0
Kotlinprogramming~10 mins

Out variance (covariance) in Kotlin - Step-by-Step Execution

Choose your learning style9 modes available
Concept Flow - Out variance (covariance)
Define interface with 'out' modifier
Use interface as return type
Assign subtype instance to supertype variable
Call method returning covariant type
Receive value safely as supertype
End
Shows how 'out' modifier allows safe return of subtype as supertype, enabling covariance.
Execution Sample
Kotlin
interface Producer<out T> {
    fun produce(): T
}

open class Fruit
class Apple : Fruit()

fun main() {
    val appleProducer: Producer<Apple> = object : Producer<Apple> {
        override fun produce() = Apple()
    }
    val fruitProducer: Producer<Fruit> = appleProducer
    val fruit: Fruit = fruitProducer.produce()
    println(fruit)
}
Shows how a Producer of Apple can be assigned to a Producer of Fruit using 'out' variance.
Execution Table
StepActionVariable/TypeValue/ResultNotes
1Define interface Producer with 'out T'Producer<out T>Covariant interfaceAllows T to be returned safely
2Define classes Fruit and AppleFruit, AppleApple inherits FruitApple is subtype of Fruit
3Create appleProducer instanceappleProducer: Producer<Apple>Instance producing AppleOverrides produce() to return Apple
4Assign appleProducer to fruitProducerfruitProducer: Producer<Fruit>fruitProducer = appleProducerAllowed due to 'out' covariance
5Call produce() on fruitProducerfruitProducer.produce()Returns Apple instanceApple is subtype of Fruit, safe to assign
6Assign produced value to fruitfruit: FruitApple instanceFruit variable holds Apple safely
7Print fruitprintln(fruit)Output: Apple@<hashcode>Shows object reference of Apple instance
💡 Program ends after printing the Apple instance as Fruit type
Variable Tracker
VariableStartAfter Step 3After Step 4After Step 5Final
appleProducerundefinedProducer<Apple> instanceProducer<Apple> instanceProducer<Apple> instanceProducer<Apple> instance
fruitProducerundefinedundefinedAssigned appleProducer (Producer<Apple>)Producer<Apple> instanceProducer<Apple> instance
fruitundefinedundefinedundefinedApple instanceApple instance
Key Moments - 3 Insights
Why can we assign Producer<Apple> to Producer<Fruit>?
Because the interface uses 'out' modifier, it is covariant. This means Producer<Apple> can be treated as Producer<Fruit> safely since it only produces (returns) values of type T.
Can we pass a Producer<Fruit> to a variable of type Producer<Apple>?
No, covariance only allows assignment from subtype to supertype, not the other way. Producer<Fruit> might produce any Fruit, not necessarily Apple.
Why is 'produce()' allowed to return Apple when the variable type is Producer<Fruit>?
Because Apple is a subtype of Fruit, returning Apple is safe when expecting Fruit. This is the essence of covariance.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution table, at which step is appleProducer assigned to fruitProducer?
AStep 3
BStep 5
CStep 4
DStep 6
💡 Hint
Check the 'Action' column for assignment operations in the execution_table.
According to variable_tracker, what is the value of 'fruit' after Step 5?
AProducer<Apple> instance
BApple instance
Cundefined
DProducer<Fruit> instance
💡 Hint
Look at the 'fruit' row and the 'After Step 5' column in variable_tracker.
If we remove 'out' from Producer<T>, what happens when assigning appleProducer to fruitProducer?
ACompilation error due to type mismatch
BAssignment works as before
CRuntime error occurs
DfruitProducer becomes null
💡 Hint
Recall that 'out' enables covariance; without it, subtype assignment is disallowed.
Concept Snapshot
interface Producer<out T> { fun produce(): T }
'out' means covariance: Producer<Apple> can be used as Producer<Fruit>
Allows safe return of subtype as supertype
Covariance works only for return types, not for input parameters
Useful for read-only producers
Full Transcript
This example shows Kotlin's out variance, also called covariance. We define an interface Producer with an out type parameter T. This means Producer is covariant in T. We create classes Fruit and Apple, where Apple inherits Fruit. We make an instance appleProducer of Producer<Apple>. Thanks to covariance, we can assign appleProducer to a variable fruitProducer of type Producer<Fruit>. When we call produce() on fruitProducer, it returns an Apple instance, which is safely assigned to a Fruit variable. This works because Apple is a subtype of Fruit, and covariance allows this safe substitution. Without 'out', this assignment would cause a compile error. Covariance is useful when you only produce (return) values of type T, ensuring type safety.