DEV Community

Venkatesh-Prasad Ranganath
Venkatesh-Prasad Ranganath

Posted on

2 1

Covariance and Contravariance

In the below Kotlin code snippet, Producer class is covariant in type parameter T and Consumer class is contravariant in type parameter T. In other words, T is a covariant type parameter of Producer and T is a covariant type parameter of Consumer. So, what do these statements mean?

To understand the meaning of these statements, let’s consider the allowed behaviors. Copy-n-paste the above code snippet at https://try.kotlinlang.org and run it. You will observe two errors — at lines 13 and 15.

class Producer<out T: Any>(val e:T) {
fun read(): T = e
}
class Consumer<in T: Any>() {
private lateinit var e: T
fun write(v: T): Unit { e = v }
}
fun main() {
var p1: Producer<Number> = Producer<Double>(0.4)
p1.read()
var p2: Producer<Double> = Producer<Number>(3) // Disallowed
p2.read()
var c1: Consumer<Number> = Consumer<Double>() // Disallowed
c1.write(3)
var c2: Consumer<Double> = Consumer<Number>()
c2.write(0.4)
}
view raw variance.kt hosted with ❤ by GitHub

Covariance

The assignment on line 11 is allowed as Producer<Double> is treated as a sub-type of Producer<Number>. This is due to two reasons.

  • Values of type T can only be retrieved/read from Producer<T> instances because T is used to specify only the values provided by Producer; see the class definition of Producer.
  • Since Producer<Double> instance is assigned to p1, all values retrieved/read via p1 will be of type Double, a sub-type of the type argument Number of the declared type Produce<Number> of p1. So, the behavior exhibited by the instance assigned to p1 conforms with (is a subset of) the behavior guaranteed by the declared type of p1.

Due to the above sub-typing relation, the assignment on line 13 is not allowed. If it was allowed, p2.read() would return a value of type Number when it should return only values of type Double based on the declared type Producer<Double> of p2.

Contravariance

A similar reasoning explains contravariance.

The assignment on line 17 is allowed as Consumer<Number> is treated as a sub-type of Consumer<Double>. This is due to two reasons.

  • Values of type T can only be injected/written into Consumer<T> instances because T is used to specify only the values provided to Consumer; see the class definition of Consumer.
  • Since the type argument Double of the declared type of c2 is a sub-type of Number, all values provided to Consumer<Number> instance assigned to c2 will be a sub-type of type Number. The behavior allowed by the declared type of c2 conforms with (is a subset of) the behavior supported by the instance assigned to c2.

Due to the above sub-typing relation, the assignment on line 15 is not allowed. If it was allowed, c1.write(3) would store a value of type Number in the property (field) c1.e when it should store only values of type Double in c1.e based on the Consumer<Double> instance assigned to c1.

Summary

In short, if type X is a sub-type of type Y and type S<X> can be treated as sub-type of type S<Y>, then S is covariant in T and T is a covariant type parameter of the generic type S<T>.

Similarly, if type X is a sub-type of type Y and S<Y> can be treated as sub-type of type S<X>, then S is contravariant in T and T is a contravariant type parameter of the generic type S<T>.

In both definitions, the key is if S<X> will be treated as sub-type of S<Y> or vice versa, and this is dependent on how the type parameter T of S<T> is used in S; specifically, in the context of input and output of the operations/methods of S.

Further Reading

Sentry mobile image

Is your mobile app slow? Improve performance with these key strategies.

Improve performance with key strategies like TTID/TTFD & app start analysis.

Read the blog post

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay