interface Consumer<in T> { fun consume(item: T) } class StringConsumer : Consumer<String> { override fun consume(item: String) { println("Consumed: $item") } } fun feedConsumer(consumer: Consumer<Any>) { consumer.consume(123) } fun main() { val stringConsumer: Consumer<String> = StringConsumer() feedConsumer(stringConsumer) }
The function feedConsumer expects a Consumer<Any>. However, stringConsumer is a Consumer<String>. Since Consumer is declared with in T (contravariant), Consumer<Any> is a subtype of Consumer<String>, not the other way around. Passing Consumer<String> where Consumer<Any> is expected causes a compile-time type mismatch error.
Contravariance in Kotlin is declared with the in keyword. It means the generic type can accept supertypes of its type parameter. For example, Consumer<in T> can consume objects of type T or any subtype, but it can be assigned to a variable expecting Consumer<SuperType>.
interface Producer<out T> { fun produce(): T } fun useProducer(producer: Producer<String>) { val anyProducer: Producer<Any> = producer println(anyProducer.produce()) } fun main() { val stringProducer: Producer<String> = object : Producer<String> { override fun produce() = "Hello" } useProducer(stringProducer) }
The Producer interface is declared with out T, making it covariant. This means Producer<String> is a subtype of Producer<Any>. Therefore, assigning producer of type Producer<String> to anyProducer of type Producer<Any> is valid and compiles.
Contravariance is declared with in in Kotlin. The interface Consumer<in T> can only consume (accept) values of type T. Option B correctly declares this. Option B uses out which is covariance. Option B is invariant. Option B is invalid because a contravariant type cannot produce values of type T.
Consumer<in T>, which assignment is valid and safe in Kotlin?interface Consumer<in T> { fun consume(item: T) } class AnyConsumer : Consumer<Any> { override fun consume(item: Any) { println("Consumed: $item") } } fun main() { val anyConsumer = AnyConsumer() // Which assignment below is valid? val stringConsumer: Consumer<String> = ??? }
Because Consumer is contravariant in T (declared with in T), Consumer<Any> can be assigned to Consumer<String>. This means anyConsumer of type Consumer<Any> can be assigned to stringConsumer of type Consumer<String>. This is safe because anyConsumer can consume any object, including Strings.