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.