Can you explain the concept of covariance and contravariance in Kotlin generics and how it affects type safety?
sobes.tech AI
Answer from AI
Contravariance in Kotlin is denoted by the keyword in and applies to type parameters in generics that are used only for accepting values (input parameters). This means that if you have a class Consumer<in T>, then Consumer<SuperType> can be assigned to a variable of type Consumer<SubType>, i.e., the direction of type substitution goes in the opposite direction of inheritance.
This helps ensure type safety by allowing the use of a more general type where a more specific one is expected, while preventing operations that could lead to runtime errors.
Example:
interface Consumer<in T> {
fun consume(item: T)
}
open class Animal
class Cat : Animal()
fun feedCat(consumer: Consumer<Cat>) {
consumer.consume(Cat())
}
val animalConsumer: Consumer<Animal> = object : Consumer<Animal> {
override fun consume(item: Animal) {
println("Feed the animal")
}
}
// Thanks to contravariance, you can pass Consumer<Animal> where Consumer<Cat> is expected
feedCat(animalConsumer)
Here, Consumer<Animal> can be used as Consumer<Cat> because Consumer is contravariant in T. This prevents errors related to incorrect type usage and allows flexible working with generic types.