Use of covariance and contravariance within functions in Kotlin.
fun main() {
val cars1 = mutableListOf(KotlinCar(), KotlinCar())
val cars2: MutableList<KotlinCar> = mutableListOf()
copyCars(cars1, cars2)
val fords1 = mutableListOf(Ford(), Ford())
val fords2: MutableList<Ford> = mutableListOf()
// copyCars(fords1, fords2) // Wants a MutableList<KotlinCar>, as consistent with covariance/invariance. Once again we don't want to have to write a copyCars() function for every type of car.
copyCars2(fords1, fords2) // Works because copyCars2 is covariant
// copyCars2(fords1, cars2) // Doesn't initially work because in each case <T> is different (Ford and KotlinCar)
val cars3: MutableList<KotlinCar> = mutableListOf(Ford(), Ford()) // Yet we should be able to as we can do this <-
// If we look at our copyCars function we can see that we only read from 'source' and only write to 'destination', so we can use co- and contravariance
copyCars3(fords1, cars1) // It works!
}
fun copyCars(source: MutableList<KotlinCar>, destination: MutableList<KotlinCar>) {
for (car in source) {
destination.add(car)
}
}
fun <T> copyCars2(source: MutableList<T>, destination: MutableList<T>) {
for (car in source) {
destination.add(car)
}
}
fun <T> copyCars3(source: MutableList<out T>, destination: MutableList<T>) { // Co- or contravariance declared in function - use-site variance, also known as type projection. Can also be used with function return types
// fun <T> copyCars3(source: MutableList<T>, destination: MutableList<in T>) { // This would also work
for (car in source) {
destination.add(car)
}
}
open class KotlinCar {
}
class Toyota: KotlinCar() {
}
class Ford: KotlinCar() {
}
