I am not able to understand and I couldn\'t find the meaning of out keyword in kotlin.
You can check example here:
List
>
The variance modifiers out
and in
allow us to make our generic types less restrictive and more reusable by allowing subtyping.
Let's understand this with the help of contrasting examples. We'll use examples of cases as containers of various weapons. Assume that we have the following type hierarchy:
open class Weapon
open class Rifle : Weapon()
class SniperRifle : Rifle()
out
produces T
and preserves subtypingWhen you declare a generic type with an out
modifier, it's called covariant. A covariant is a producer of T
, that means functions can return T
but they can't take T
as arguments:
class Case {
private val contents = mutableListOf()
fun produce(): T = contents.last() // Producer: OK
fun consume(item: T) = contents.add(item) // Consumer: Error
}
The Case
declared with the out
modifier produces T
and its subtypes:
fun useProducer(case: Case) {
// Produces Rifle and its subtypes
val rifle = case.produce()
}
With the out
modifier, the subtyping is preserved, so the Case
is a subtype of Case
when SniperRifle
is a subtype of Rifle
. As a result, the useProducer()
function can be called with Case
too:
useProducer(Case()) // OK
useProducer(Case) // OK
useProducer(Case()) // Error
This is less restrictive and more reusable while producing but our class becomes read only.
in
consumes T
and reverses subtypingWhen you declare a generic type with an in
modifier, it's called contravariant
. A contravariant is a consumer of T
, that means functions can take T
as arguments but they can't return T
:
class Case {
private val contents = mutableListOf()
fun produce(): T = contents.last() // Producer: Error
fun consume(item: T) = contents.add(item) // Consumer: OK
}
The Case
declared with the in
modifier consumes T
and its subtypes:
fun useConsumer(case: Case) {
// Consumes Rifle and its subtypes
case.consume(SniperRifle())
}
With the in
modifier, the subtyping is reversed, so now the Case
is a subtype of Case
when Rifle
is a subtype of Weapon
. As a result, the useConsumer()
function can be called with Case
too:
useConsumer(Case()) // Error
useConsumer(Case()) // OK
useConsumer(Case()) // OK
This is less restrictive and more reusable while consuming but our class becomes write only.
T
, disallows subtypingWhen you declare a generic type without any variance modifier, it's called invariant. An invariant is a producer as well as a consumer of T
, that means functions can take T
as arguments and can also return T
:
class Case {
private val contents = mutableListOf()
fun produce(): T = contents.last() // Producer: OK
fun consume(item: T) = contents.add(item) // Consumer: OK
}
The Case
declared without in
or out
modifier produces and consumes T
and its subtypes:
fun useProducerConsumer(case: Case) {
// Produces Rifle and its subtypes
case.produce()
// Consumes Rifle and its subtypes
case.consume(SniperRifle())
}
Without the in
or out
modifier, the subtyping is disallowed, so now neither Case
nor Case
is a subtype of Case
. As a result, the useProducerConsumer()
function can only be called with Case
:
useProducerConsumer(Case()) // Error
useProducerConsumer(Case()) // OK
useProducerConsumer(Case()) // Error
This is more restrictive and less reusable while producing and consuming but we can read and write.
The List
in Kotlin is a producer only. Because it's declared using the out
modifier: List
. This means you cannot add elements to it as the add(element: T)
is a consumer function. Whenever you want to be able to get()
as well as add()
elements, use the invariant version MutableList
.
That's it! Hopefully that helps understand the in
s and out
s of the variance!