Introduction
Kotlin variance modifiers impose limits on the type of parameters used. Covariant type parameter (with out modifier) cannot be used in public places, and contravariant type parameter (with in modifier) cannot be used outside public places. But why introduce such limitations?
Let’s find out about it.
Variance modifiers
We have published an article explaining variance modifiers in depth. It can be found here . It can be summed up briefly:
When a generic type is an indeterminate variant, like the Box <T> class , there is no relationship between Box <SomeType> and Box <AnotherType> . Therefore there is no relationship between Box <Number> and Box <Int>
When a generic type is covariant, like the Box <out T> class , when A is a subtype of B then Box <A> is a subtype of Box <B> . Therefore Box <Int> is a subtype of Box <Number> .
When a generic type is contravariant, like Box <int T> , when A is a subtype of B then Box <A> is a subtype of Box <A> . Therefore Box <Number> is a subtype of Box <Int>
Short summary:
Review limitations
Even Kotlin introduces some limitations to type parameters using variance modifiers. The layer below is exactly correct.
1 2 3 4 5 6 7 8 9 10 11 12 | lass SomeClass<T> { var t: T? = null fun functionReturningT(): T? = t fun functionAcceptingT(t: T) {} private fun privateFunctionReturningT(): T? = t private fun privateFunctionAcceptingT(t: T) {} } |
Even it won’t compile if we introduce any variance modifier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class SomeClass<out T> { var t: T? = null // Error private var pt: T? = null fun functionReturningT(): T? = t fun functionAcceptingT(t: T) {} // Error private fun privateFunctionReturningT(): T? = t private fun privateFunctionAcceptingT(t: T) {} } class SomeClass<in T> { var t: T? = null // Error private var pt: T? = null fun functionReturningT(): T? = t // Error fun functionAcceptingT(t: T) {} private fun privateFunctionReturningT(): T? = t private fun privateFunctionAcceptingT(t: T) {} } |
As you can see, covariant is not used for public methods as a parameter type, and it cannot be used for read / write public properties. Only read is okay because they only reveal the location:
1 2 3 4 5 6 7 8 9 10 11 12 | class SomeClass<out T> { val t: T? = null private var pt: T? = null fun functionReturningT(): T? = t private fun privateFunctionReturningT(): T? = t private fun privateFunctionAcceptingT(t: T) {} } |
Contravariance cannot be used as a return type from methods and applies on all methods (Getter clarity is equivalent to attribute clarity).
Example problem
To understand the problem behind these limits, think of Java arrays. They are covariant and, at the same time, they allow to set values (by location). As a result, you can call against the source code which is perfectly correct from the point of view compilation, but will always result in a runtime error:
1 2 3 4 5 | // Java Integer[] ints = { 1,2,3 }; Object[] objects = ints; objects[2] = "AAA"; |
What happened here? We cast the opposite type (up-casting – Type cast into the parent object) array and then set the type down-type (down-casting – Type cast into a child object) and so boom! We have an error. How does it relate to location?
Positions and typing
In-position and out-position have some standard casting rules. See architecture type below.
When we need to get Dog for the input position (in-position) – parameter for takeDog (dog: Dog) method , every subtype is also accepted.
1 2 3 4 | fun takeDog(dog: Dog) {}takeDog(Dog()) takeDog(Puppy()) takeDog(Hund()) |
When we retrieve Dog from the output position, the accepted values are Dog simultaneously for all subtypes.
1 2 3 4 | fun makeDog(): Dog = Dog()val any: Any = makeDog() val animal: Animal = makeDog() val wild: Wild = makeDog() |
Notice that once the element is in or out of position, the different types of casting are default, and it cannot be stopped.
This happens in our array example. Covariance allows for reverse (up-casting), and in-position allows for down-casting. The process of using these two techniques at the same time we can cast into everything. The same goes for Contravariance and out-position. Also, allow the developer to force any type into any other type. The only problem is that if an actual type cannot be forced in this way then we have a runtime error.
The only way to avoid this is to prohibit the connection between public in-position with contravariacne and public out-position with variance. This is why Kotlin has these limitations. Kotlin also solved the array problem by creating all arrays invariant. It is another example of how Kotlin is a safer language than Java (see this presentation ).
Source
Reference
https://blog.kotlin-academy.com/kotlin-generics-variance-modifiers-36b82c7caa39 https://blog.kotlin-academy.com/kotlin-next-level-of-android-development-95bce2f43a24 https: // blog.kotlin-academy.com/programmer-dictionary-parameter-vs-argument-type-parameter-vs-type-argument-b965d2cc6929
VII. P / S
If my viblo posts have a Source , this is a translation from the source that is linked to the original post in this section. These are the articles I selected + searched + synthesized from Google in the process of dealing with issues when doing practical projects + useful and interesting for myself. => Translate as an article to rummage through when needed. So when reading the article, please note: