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
T
can only be retrieved/read fromProducer<T>
instances becauseT
is used to specify only the values provided byProducer
; see the class definition ofProducer
. - Since
Producer<Double>
instance is assigned top1
, all values retrieved/read viap1
will be of typeDouble
, a sub-type of the type argumentNumber
of the declared typeProduce<Number>
ofp1
. So, the behavior exhibited by the instance assigned top1
conforms 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
T
can only be injected/written intoConsumer<T>
instances becauseT
is used to specify only the values provided toConsumer
; see the class definition ofConsumer
. - Since the type argument
Double
of the declared type ofc2
is a sub-type ofNumber
, all values provided toConsumer<Number>
instance assigned toc2
will be a sub-type of typeNumber
. The behavior allowed by the declared type ofc2
conforms 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)