Understanding how contravariance affects the ability to use Generics in Kotlin.
fun main() {
val roseTender = object: FlowerCare2<Rose2> {
override fun prune(flower: Rose2) {
println("I'm pruning a rose!")
}
}
val daffodilTender = object: FlowerCare2<Daffodil2> {
override fun prune(flower: Daffodil2) {
println("I'm pruning a daffodil!")
}
}
val roseGarden = Garden2(listOf(Rose2("Rosemary"), Rose2("Rosie")), roseTender)
println(roseGarden.javaClass)
val daffodilGarden = Garden2(listOf(Daffodil2(), Daffodil2(), Daffodil2()), daffodilTender)
println(roseGarden.javaClass)
roseGarden.tendFlower(0)
daffodilGarden.tendFlower(2) // This is rather repetitive. We have to create Gardens and call tendFlower, but roseTender and daffodilTender are doing the same thing
val flowerTender = object : FlowerCare2<Flower2> { // Create a generic flowerTender that can handle any type of Flower2
override fun prune(flower: Flower2) {
println("I'm tending a specifically-named ${flower.name}!")
}
}
val roseGarden2 = Garden2(listOf(Rose2("Rosemary"), Rose2("Rosie")), flowerTender) // Won't work without adding 'in' to FlowerCare2 class, as expects FlowerCare2<Rose2>. We need <T> matching to be more relaxed.
// Covariance preserves the subtyping. Contravariance is the opposite - starting with subclass and wanting to accept superclasses.
// Comes at a price. We can only write them and not read from them. Cannot use them as return types from functions.
roseGarden2.tendFlower(1)
}
class Garden2<T: Flower2>(val flowers: List<T>, val flowerCare: FlowerCare2<T>) {
fun pickFlower(i: Int) = flowers[i]
fun tendFlower(i: Int) {
flowerCare.prune(flowers[i])
}
}
open class Flower2(val name: String) {
}
class Rose2(val roseName: String): Flower2(roseName) {
}
class Daffodil2: Flower2("Daffodil") {
}
interface FlowerCare2<in T> { // Made contravariant by 'in' keyword. Will accept T or any superclass of.
// As it is done in the declaration is known as 'declaration-site variance'. Java only has 'use-site variance' (see post of same name).
fun prune(flower: T)
// fun pick(): T // Cannot return this Generic as it is now contravariant
}
