liveBook · Manning

Invariant generic class

As explained in Kotlin in Action, 2E

An invariant type does not allow a parameterized type to substitute a subtype or supertype for its type argument.

An invariant generic class does not relate different type arguments through subtyping: neither MutableList<String> nor MutableList<Any> is a subtype of the other. This prevents unsafe substitutions when a class can both produce and consume values. Java classes are invariant by default, while Kotlin separately supports covariant and contravariant declarations. section 11.3.2 section 11.3.3

A collection-like class remains invariant

```kotlin open class Animal { fun feed() { /* ... */ } } class Herd<T : Animal> { val size: Int get() = /* ... */ operator fun get(i: Int): T { /* ... */ } } fun feedAll(animals: Herd<Animal>) { /* ... */ } class Cat : Animal() { fun cleanLitter() { /* ... */ } } fun takeCareOfCats(cats: Herd<Cat>) { cats[0].cleanLitter() // feedAll(cats) // type mismatch } ``` Because `Herd<T>` has no variance modifier, `Herd<Cat>` is not a subtype of `Herd<Animal>`. An explicit cast may force the call, but it is described as verbose, error-prone, and generally the wrong response to this mismatch.

Invariance can be relaxed at a particular use site when an invariant class is used in only one direction. An out-projected MutableList<out T> can be read as a producer, while MutableList<in T> can be used as a consumer. The restriction is visible in the API: an out-projected list cannot accept an element through add. section 11.3.5

Copying between invariant lists

```kotlin fun <T> copyData( source: MutableList<out T>, destination: MutableList<T> ) { for (item in source) destination.add(item) } val ints = mutableListOf(1, 2, 3) val anyItems = mutableListOf<Any>() copyData(ints, anyItems) ``` The source is restricted to reading and the destination to writing, so values from a more specific list can be copied into a list whose element type is a supertype.

How Kotlin in Action, 2E covers it

How deep it goes
Defines invariant subtyping, demonstrates the resulting mismatch, and defers broader variance rules to covariance and projection.
Examples
Uses Herd<Cat> versus Herd<Animal>, MutableList copying, and an out-projected list that rejects add.
What it assumes
Assumes generic types and basic subtyping, while establishing the distinction between subclasses, subtypes, and nullable types.
What you can do after
Apply invariant and projected collection types while recognizing when a generic type mismatch signals an unsafe substitution.
How it connects
Connects generic types with subtyping, covariance, contravariance, type projection, mutable collections, and Java variance conventions.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Kotlin in Action, 2E. The section links point to the parts of the book it draws on.