1. Use site variance
Sample: list with contravariance acts as a consumer.
It is safe to add into list the object of declared type B, or it's subtypes. It is not safe to retrieve object of type B from the list, because list of objects with super types of B may be provided. However, it is safe to retrieve the Kotlin most basic type Any? from the list.
open class A
open class B : A()
class C : B()
// Can receive list of B or its super types (A, Any, Any? types in this sample).
// Allows to put B or B subtypes into list (C in this sample).
// Cannot get fom List - except, can only get most generic type Any? from list.
fun listAsConsumer(list: MutableList<in B>) {
// Type mismatch: inferred type is Any? but B was expected
// val b: B = list[0]
// Type mismatch: inferred type is A but B was expected
// list.add(A())
// Cannot get fom List - except, can only get most generic type Any? from list.
val any: Any? = list[0]
// Allowed to add B or its subtypes into list
list.add(B())
list.add(C())
}
fun main() {
listAsConsumer(mutableListOf<A>(A()))
}
Sample: contravariance in Comparator.
Compare list of Ints using Comparator<Number>.
fun <T> Iterable<T>.sortedWithWrapper(comparator: Comparator<in T>): List<T> {
return this.sortedWith(comparator)
}
fun main() {
val comparator: Comparator<Number> = Comparator { o1: Number, o2: Number ->
o1.toDouble().compareTo(o2.toDouble())
}
val intList: List<Int> = listOf(4, 7)
val result: List<Int> = intList.sortedWithWrapper(comparator)
}
The same sample without contravariance for Comparator is below. Without contravariance sortedWithWrapper() function could not return List<Int>, but only List<Number>. This happens because List<Int> is upcasted to List<Number>, to match the invariant T of the Comparator.
fun <T> Iterable<T>.sortedWithWrapper(comparator: Comparator<T>): List<T> {
return this.sortedWith(comparator)
}
fun main() {
val comparator: Comparator<Number> = Comparator { o1: Number, o2: Number ->
o1.toDouble().compareTo(o2.toDouble())
}
val intList: List<Int> = listOf(4, 7)
// Type mismatch: inferred type is List<Number> but List<Int> was expected
// val result: List<Int> = intList.sortedWithWrapper(comparator)
val result: List<Number> = intList.sortedWithWrapper(comparator)
}
2. Declaration site variance
Declaration site variance is available in Kotlin, unlike Java where only use-site variance is possible.
Sample: Class with contravariance. Consumer cannot have contravariant type T in invariant or out positions.
open class A
open class B : A()
class C : B()
class Consumer<in T> {
// Type parameter T is declared as 'in' but occurs in 'invariant'
// position in type T?
// var x: T? = null
fun consume(x: T) {}
// Ok, List is defined as List<out T>,
// so it is a producer for this Consumer class
fun consume(x: List<T>) {}
// Type parameter T is declared as 'in' but occurs in 'invariant' position
// in type Array<T>
// fun consume(x: Array<T>) {}
// Type parameter T is declared as 'in' but occurs in 'out' position
// in type T?
// fun produce() : T? { return x }
}
fun main() {
// Consumer of B (can be assigned Consumer of supertypes (A, Any, Any?)),
// but now minimum B or its subtypes are accepted into consume()
val consumerB: Consumer<B> = Consumer<A>()
consumerB.consume(C())
// Type mismatch: inferred type is A but B was expected
// consumerC.consume(A())
// Type mismatch: inferred type is Consumer<B> but Consumer<A> was expected
// val consumerA: Consumer<A> = Consumer<B>()
}
Consumer class with contravariant T cannot accept another consumer with contravariant T.
class AnotherConsumer<in T> {
fun consume() {}
}
class Consumer<in T> {
// Type parameter T is declared as 'in' but occurs in 'out' position
// in type AnotherConsumer
// fun consume(x: AnotherConsumer<T>) {}
}
This is due to AnotherConsumer can be extended by the class that cancels the contravariant 'in' modifier:
open class AnotherConsumer<in T> {
open fun consume(x: T) {}
}
class AnotherConsumerChild: AnotherConsumer<T>() {
var x: T? = null
override fun consume(x: T) {
this.x = x
}
fun getT(): T? {
return x
}
}
Allowing to accept another contravariant consumer would lead to ClassCastException.
As an example of the above, in this snippet AnotherConsumerC would be called with the wrong type A (omitting compiler variance checking with @UnsafeVariance annotation).
open class A
open class B : A()
class C : B() {
fun c() {}
}
open class AnotherConsumer<in T> {
open fun consume(x: T) {}
}
// Overrides contravariance in parent type
class AnotherConsumerC: AnotherConsumer<C>() {
override fun consume(x: C) {
x.c()
}
}
open class Consumer<in T> {
open fun consume(anotherConsumer: AnotherConsumer<@UnsafeVariance T>) {}
}
open class ConsumerA: Consumer<A>() {
override fun consume(anotherConsumer: AnotherConsumer<A>) {
anotherConsumer.consume(A())
}
}
fun main() {
val consumerA = ConsumerA()
// Allowed because parent Consumer<in T> is contravariant
val consumerC: Consumer<C> = consumerA
// Produces Exception in thread "main"
// java.lang.ClassCastException: A cannot be cast to C
consumerC.consume(AnotherConsumerC())
}
No comments :
Post a Comment