Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Understanding Variance in Kotlin Generics

What is Variance?

Variance is a concept in type theory that deals with how subtyping between more complex types relates to subtyping between their components. In Kotlin, variance is primarily concerned with generics and allows developers to define how type parameters behave in relation to their subtypes. There are three types of variance: covariance, contravariance, and invariance.

Covariance

Covariance allows a generic type to be a subtype of another generic type when its type parameter is a subtype of the other type's parameter. In Kotlin, covariance is indicated using the keyword out.

Example of Covariance

Consider the following example:

interface Producer { fun produce(): T }

Here, Producer can accept a subtype of T. For instance:

class Animal
class Dog : Animal()
class Cat : Animal()
class DogProducer : Producer { override fun produce(): Dog = Dog() }

Now, Producer can be treated as Producer because Dog is a subtype of Animal.

Contravariance

Contravariance is the opposite of covariance. It allows a generic type to accept a supertype of its type parameter. In Kotlin, contravariance is indicated using the keyword in.

Example of Contravariance

Consider the following example:

interface Consumer { fun consume(item: T) }

Here, Consumer can accept a supertype of T. For instance:

class Animal
class Dog : Animal()
class AnimalConsumer : Consumer { override fun consume(item: Animal) { /* consume animal */ } }

In this case, Consumer can also be used as Consumer because Animal is a supertype of Dog.

Invariance

Invariance means that a generic type is not subtyped by its generic type parameters. This means that List is not a subtype of List even though Dog is a subtype of Animal. Invariance is the default behavior in Kotlin.

Example of Invariance

Consider the following example:

class Box(var item: T)

Here, Box is not a subtype of Box even though Dog is a subtype of Animal.

Summary

In Kotlin, understanding variance is crucial for effective use of generics. Covariance allows subtyping for producing types, contravariance allows subtyping for consuming types, and invariance means no subtyping is allowed between generics. Proper use of these concepts can lead to more flexible and reusable code.