What is the hell of invariance, covariance and contravariance in Kotlin , and why this happen. It’s also helpful for understanding wildcast in Java.
Invariance, Why in
and out
mechanism
When you call addAnswer()
, the generic type must be Number
, otherwise, you will get a Type mismatch: inferred type is MutableList<Int> but MutableList<Number> was expected
alike compile time error.
1 | fun addAnswer(list: MutableList<Number>) { |
There are two and only two conditions if you don’t provide the same type declared in function parameter.
- type is more specific. The MutableList\<Int> do not take a Double value.
- type is more general. There is no guarantee on what you get from the collection is a number or something else.
This is called invariance.
In the following part, we are using Producer-Consumer problem as sample to look inside contravariance and covariance.
There is no doubt that generic is powerful and flexible. The invariance of collection is a big trouble. And the implementation of generic in Java , type info be erased after compile makes it much harder to handle this.
out
, Covariance
1 | interface Producer<out T> { // notice the `out` notation |
1 | val producerAnimal = object : Producer<Animal> { |
By adding out
notation at the generic declaration, now we can using instances whose generic type is a subtype of the expected generic type. As is when Producer<Number>
is expected, you can pass Producer<Int>
as well, but Producer<Any>
is unacceptable.
The out
notation can get rid of the first risk(type is more specific. The MutableList\<Int> do not take a Double value. ) introduced in the former part. Because we do not use these as args, so we are not gonna violet the first limitation.
in
, Contravariance
1 | interface Consumer<in T> { |
1 | val consumerCreature = object : Consumer<Creature> { |
By adding in
notation , more general type becomes acceptable. This get rid of the second risk. That is it.
in
and out
are confusing sometimes, just like InputStream
and OutputStream
. You can simple consider using type in somewhat return statement
as out
, using type as args or parameter type
as in
. Ninth chapter of Kotlin in Action explained this in detail.
This post is mainly explain invariance, covariance and contravariance, and why this happen. For more detail , have a look at Kotlin in Action and Kotlin documentation.