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.
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
Tcan only be retrieved/read fromProducer<T>instances becauseTis used to specify only the values provided byProducer; see the class definition ofProducer. - Since
Producer<Double>instance is assigned top1, all values retrieved/read viap1will be of typeDouble, a sub-type of the type argumentNumberof the declared typeProduce<Number>ofp1. So, the behavior exhibited by the instance assigned top1conforms with (is a subset of) the behavior guaranteed by the declared type ofp1.
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
Tcan only be injected/written intoConsumer<T>instances becauseTis used to specify only the values provided toConsumer; see the class definition ofConsumer. - Since the type argument
Doubleof the declared type ofc2is a sub-type ofNumber, all values provided toConsumer<Number>instance assigned toc2will be a sub-type of typeNumber. The behavior allowed by the declared type ofc2conforms with (is a subset of) the behavior supported by the instance assigned toc2.
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.
Top comments (0)