Covariance in Generics
Generics are powerful. Type inheritance is powerful. Sometimes, powerful things do not get along very well… and that can be the case with generics and inheritance.
In this chapter and the next, we will explore why sometimes Kotlin complains about your generics, and how two simple keywords — in
and out
— can help solve the problem.
You Can’t Put That in That!
open class Animal
class Frog : Animal()
class Axolotl : Animal()
fun main() {
val animals: List<Animal> = listOf<Frog>(Frog(), Frog())
println(animals)
}
Here, we have two sub-types of Animal
: Frog
and Axoltl
. We create a List<Animal>
variable named animals
and try initializing it with a List<Frog>
. Frogs are animals, and Frog
is an Animal
, so we would expect this to work… and it does.
However, simply switching from List
to MutableList
causes problems:
open class Animal
class Frog : Animal()
class Axolotl : Animal()
fun main() {
val animals: MutableList<Animal> = mutableListOf<Frog>(Frog(), Frog())
println(animals)
}
If you try this, you will get Type mismatch: inferred type is Animal but Frog was expected
as a compile error.
If you look at the Kotlin documentation for MutableList
, you will see that it is declared as:
interface MutableList<E> : List<E>, MutableCollection<E>
MutableList
extends the List
and MutableCollection
interfaces, using a generic type E
.
The Kotlin documentation for List
, though, is a little bit strange:
interface List<out E> : Collection<E>
WTF is out
?
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.