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.