List<out T>
in Kotlin is equivalent to List<? extends T>
in Java.
List<in T>
in Kotlin is equivalent to List<? super T>
in Java
For example in Kotlin you can do things like
val value : List<Any> = listOf(1,2,3)
//since List signature is List<out T> in Kotlin
With this signature:
List<out T>
you can do this:
val doubleList: List<Double> = listOf(1.0, 2.0)
val numberList: List<Number> = doubleList
which means T is covariant:
when a type parameter T of a class C is declared out, C<Base> can safely be a supertype of C<Derived>.
This is contrast with in, e.g.
Comparable<in T>
you can do this:
fun foo(numberComparable: Comparable<Number>) {
val doubleComparable: Comparable<Double> = numberComparable
// ...
}
which means T is contravariant:
when a type parameter T of a class C is declared in, C<Derived> can safely be a supertype of C<Base>.
Another way to remember it:
Consumer in, Producer out.
see Kotlin Generics Variance
-----------------updated on 4 Jan 2019-----------------
For the "Consumer in, Producer out", we only read from Producer - call method to get result of type T; and only write to Consumer - call method by passing in parameter of type T.
In the example for List<out T>
, it is obvious that we can do this:
val n1: Number = numberList[0]
val n2: Number = doubleList[0]
So it is safe to provide List<Double>
when List<Number>
is expected, hence List<Number>
is super type of List<Double>
, but not vice versa.
In the example for Comparable<in T>
:
val double: Double = 1.0
doubleComparable.compareTo(double)
numberComparable.compareTo(double)
So it is safe to provide Comparable<Number>
when Comparable<Double>
is expected, hence Comparable<Double>
is super type of Comparable<Number>
, but not vice versa.
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<out T> {
private val contents = mutableListOf<T>()
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<Rifle>) {
// Produces Rifle and its subtypes
val rifle = case.produce()
}
With the out
modifier, the subtyping is preserved, so the Case<SniperRifle>
is a subtype of Case<Rifle>
when SniperRifle
is a subtype of Rifle
. As a result, the useProducer()
function can be called with Case<SniperRifle>
too:
useProducer(Case<SniperRifle>()) // OK
useProducer(Case<Rifle>) // OK
useProducer(Case<Weapon>()) // 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<in T> {
private val contents = mutableListOf<T>()
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<Rifle>) {
// Consumes Rifle and its subtypes
case.consume(SniperRifle())
}
With the in
modifier, the subtyping is reversed, so now the Case<Weapon>
is a subtype of Case<Rifle>
when Rifle
is a subtype of Weapon
. As a result, the useConsumer()
function can be called with Case<Weapon>
too:
useConsumer(Case<SniperRifle>()) // Error
useConsumer(Case<Rifle>()) // OK
useConsumer(Case<Weapon>()) // 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<T> {
private val contents = mutableListOf<T>()
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<Rifle>) {
// 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<Weapon>
nor Case<SniperRifle>
is a subtype of Case<Rifle>
. As a result, the useProducerConsumer()
function can only be called with Case<Rifle>
:
useProducerConsumer(Case<SniperRifle>()) // Error
useProducerConsumer(Case<Rifle>()) // OK
useProducerConsumer(Case<Weapon>()) // 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<out T>
. 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<T>
.
That's it! Hopefully that helps understand the in
s and out
s of the variance!
These answers explain what out
does, but not why you need it, so let's pretend we didn't have out
at all. Imagine three classes: Animal, Cat, Dog, and a function taking a list of Animal
abstract class Animal {
abstract fun speak()
}
class Dog: Animal() {
fun fetch() {}
override fun speak() { println("woof") }
}
class Cat: Animal() {
fun scratch() {}
override fun speak() { println("meow") }
}
Since a Dog
is a subtype of Animal
, we want to use List<Dog>
as a subtype of List<Animal>
which means we want to be able to do this:
fun allSpeak(animals: List<Animal>) {
animals.forEach { it.speak() }
}
fun main() {
val dogs: List<Dog> = listOf(Dog(), Dog())
allSpeak(dogs)
val mixed: List<Animal> = listOf(Dog(), Cat())
allSpeak(mixed)
}
And that's fine, the code will print woof woof
for the dogs and woof meow
for the mixed list.
The problem is when we have a mutable container. Since a List<Animal>
can contain Dog
and Cat
, we can add either to a MutableList<Animal>
fun processAnimals(animals: MutableList<Animal>) {
animals.add(Cat()) // uh oh, what if this is a list of Dogs?
}
fun main() {
val dogs: MutableList<Dog> = mutableListOf(Dog(), Dog())
processAnimals(dogs) // we just added a Cat to a list of Dogs!
val d: Dog = dogs.last() // list of Dogs, so return type of .last() is Dog
// but this is actually a Cat
d.fetch() // a Cat can't fetch, so what should happen here?
}
You can't safely consider MutableList<Dog>
to be a subtype of MutableList<Animal>
because you can do things to the latter (insert a cat) which you can't do to the former.
As a more extreme example:
val dogs: MutableList<Dog> = mutableListOf(Dog())
val anything: MutableList<Any> = dogs
// now I can add any type I want to the dogs list through the anything list
anything.add("hello world")
The problem only occurs when adding to the list, not reading from it. It is safe to use List<Dog>
as List<Animal>
because you can't append to List
. This is what out
tells us. out
says "this is a type which I output, but I don't take it as new inputs which I consume"
Remember like this:
in
is "for input" - you wanna put(write) something into it (so it's a "consumer")
out
is "for output" - you wanna take(read) something out of it (so it's a "producer")
If you're from Java,
<in T>
is for input, so it's like <? super T>
(consumer)
<out T>
is for output, so it's like <? extends T>
(producer)
Refer to thie manual of kotlin
The Kotlin
List<out T>
type is an interface that provides read only operations like size, get and so on. Like in Java, it inherits fromCollection<T>
and that in turn inherits fromIterable<T>
. Methods that change the list are added by theMutableList<T>
interface. This pattern holds also forSet<out T>/MutableSet<T>
andMap<K, out
V>/MutableMap<K, V>
And this,
In Kotlin, there is a way to explain this sort of thing to the compiler. This is called declaration-site variance: we can annotate the type parameter T of Source to make sure that it is only returned (produced) from members of
Source<T>
, and never consumed. To do this we provide the out modifier:> abstract class Source<out T> { > abstract fun nextT(): T } > > fun demo(strs: Source<String>) { > val objects: Source<Any> = strs // This is OK, since T is an out-parameter > // ... }
The general rule is: when a type parameter
T
of a classC
is declared out, it may occur only in out-position in the members ofC
, but in returnC<Base>
can safely be a supertype ofC<Derived>
.In "clever words" they say that the class
C
is covariant in the parameterT
, or thatT
is a covariant type parameter. You can think of C as being a producer of T's, and NOT a consumer ofT
's. The out modifier is called a variance annotation, and since it is provided at the type parameter declaration site, we talk about declaration-site variance. This is in contrast with Java's use-site variance where wildcards in the type usages make the types covariant.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With