
Puppy is a subtype of Dog, and you have a generic Box class to enclose them both. The question is: what is the relation between the Box<Puppy> and Box<Dog> types? In other words, can we use Box<Puppy> where Box<Dog> is expected, or vice versa? To answer these questions, we need to know what the variance modifier of this class type parameter is[^01_1].out or in modifier), we say it is invariant and thus expects an exact type. So, if we have class Box<T>, then there is no relation between Box<Puppy> and Box<Dog>.class Box<T> open class Dog class Puppy : Dog() fun main() { val d: Dog = Puppy() // Puppy is a subtype of Dog val bd: Box<Dog> = Box<Puppy>() // Error: Type mismatch val bp: Box<Puppy> = Box<Dog>() // Error: Type mismatch val bn: Box<Number> = Box<Int>() // Error: Type mismatch val bi: Box<Int> = Box<Number>() // Error: Type mismatch }
Box<Puppy> and Box<Dog>. When we use the out modifier, we make a covariant type parameter. When A is a subtype of B, the Box type parameter is covariant (out modifier) and the Box<A> type is a subtype of Box<B>. So, in our example, for class Box<out T>, the Box<Puppy> type is a subtype of Box<Dog>.class Box<out T> open class Dog class Puppy : Dog() fun main() { val d: Dog = Puppy() // Puppy is a subtype of Dog val bd: Box<Dog> = Box<Puppy>() // OK val bp: Box<Puppy> = Box<Dog>() // Error: Type mismatch val bn: Box<Number> = Box<Int>() // OK val bi: Box<Int> = Box<Number>() // Error: Type mismatch }
in modifier, we make a contravariant type parameter. When A is a subtype of B and the Box type parameter is contravariant (in modifier), then type Box<B> is a subtype of Box<A>. So, in our example, for class Box<in T> the Box<Dog> type is a subtype of Box<Puppy>.class Box<in T> open class Dog class Puppy : Dog() fun main() { val d: Dog = Puppy() // Puppy is a subtype of Dog val bd: Box<Dog> = Box<Puppy>() // Error: Type mismatch val bp: Box<Puppy> = Box<Dog>() // OK val bn: Box<Number> = Box<Int>() // Error: Type mismatch val bi: Box<Int> = Box<Number>() // OK }

Animal and its subclass Cat. You also have the standalone function petAnimals, which you use to pet all your animals when you get back home. You also have a list of cats that is of type List<Cat>. The question is: can you use your list of cats as an argument to the function petAnimals, which expects a list of animals?interface Animal { fun pet() } class Cat(val name: String) : Animal { override fun pet() { println("$name says Meow") } } fun petAnimals(animals: List<Animal>) { for (animal in animals) { animal.pet() } } fun main() { val cats: List<Cat> = listOf(Cat("Mruczek"), Cat("Puszek")) petAnimals(cats) // Can I do that? }
List interface type parameter is covariant, so it has the out modifier, which is why List<Cat> can be used where List<Animal> is expected.
out) is a proper variance modifier because List is read-only. Covariance can’t be used for a mutable data structure. The MutableList interface has an invariant type parameter, so it has no variance modifier.
MutableList<Cat> cannot be used where MutableList<Animal> is expected. There are good reasons for this which we will explore when we discuss the safety of variance modifiers. For now, I will just show you an example of what might go wrong if MutableList were covariant: we could use MutableList<Cat> where MutableList<Animal> is expected and then use this reference to add Dog to our list of cats. Someone would be really surprised to find a dog in a list of cats.interface Animal class Cat(val name: String) : Animal class Dog(val name: String) : Animal fun addAnimal(animals: MutableList<Animal>) { animals.add(Dog("Cookie")) } fun main() { val cats: MutableList<Cat> = mutableListOf(Cat("Mruczek"), Cat("Puszek")) addAnimal(cats) // COMPILATION ERROR val cat: Cat = cats.last() // If code would compile, it would break here }
out suggests, is appropriate for types that are only exposed and only go out of an object but never go in. So, covariance should be used for immutable classes.interface Sender<T : Message> { fun send(message: T) } interface Message interface OrderManagerMessage : Message class AddOrder(val order: Order) : OrderManagerMessage class CancelOrder(val orderId: String) : OrderManagerMessage interface InvoiceManagerMessage : Message class MakeInvoice(val order: Order) : OrderManagerMessage
GeneralSender that is capable of sending any kind of message. The question is: can you use GeneralSender where a class for sending some specific kind of messages is expected? You should be able to! If GeneralSender can send all kinds of messages, it should be able to send specific message types as well.class GeneralSender( serviceUrl: String ) : Sender<Message> { private val connection = makeConnection(serviceUrl) override fun send(message: Message) { connection.send(message.toApi()) } } val orderManagerSender: Sender<OrderManagerMessage> = GeneralSender(ORDER_MANAGER_URL) val invoiceManagerSender: Sender<InvoiceManagerMessage> = GeneralSender(INVOICE_MANAGER_URL)
in modifier.interface Sender<in T : Message> { fun send(message: T) }
Number, we can assume it can consume objects of type Int or Float. If a class consumes anything, it should consume strings or chars, therefore its type parameter, which represents the type this class consumes, must be contravariant, so use the in modifier.class Consumer<in T> { fun consume(value: T) { println("Consuming $value") } } fun main() { val numberConsumer: Consumer<Number> = Consumer() numberConsumer.consume(2.71) // Consuming 2.71 val intConsumer: Consumer<Int> = numberConsumer intConsumer.consume(42) // Consuming 42 val floatConsumer: Consumer<Float> = numberConsumer floatConsumer.consume(3.14F) // Consuming 3.14 val anyConsumer: Consumer<Any> = Consumer() anyConsumer.consume(123456789L) // Consuming 123456789 val stringConsumer: Consumer<String> = anyConsumer stringConsumer.consume("ABC") // Consuming ABC val charConsumer: Consumer<Char> = anyConsumer charConsumer.consume('M') // Consuming M }
out modifier is only appropriate for type parameters that are in the out-position and are thus used as a result type or a read-only property type. On the other hand, the in modifier is only appropriate for type parameters that are in the in-position and are thus used as parameter types.Int and returns an Any:fun printProcessedNumber(transformation: (Int) -> Any) { println(transformation(42)) }
(Int)->Any, but it would work with (Int)->Number, (Number)->Any, (Number)->Number, (Any)->Number, (Number)->Int, etc.val intToDouble: (Int) -> Number = { it.toDouble() } val numberAsText: (Number) -> String = { it.toString() } val identity: (Number) -> Number = { it } val numberToInt: (Number) -> Int = { it.toInt() } val numberHash: (Any) -> Number = { it.hashCode() } printProcessedNumber(intToDouble) printProcessedNumber(numberAsText) printProcessedNumber(identity) printProcessedNumber(numberToInt) printProcessedNumber(numberHash)


Kotlin type hierarchy
in variance modifier suggests. All return types in Kotlin function types are covariant, as the name of the out variance modifier suggests.
out modifier. Type parameters that are only used for public in-positions (function parameter types) should be contravariant so they have an in modifier.T in class Box<T> or fun a<T>() {}. The type argument is the actual type used when a class is created or a function is called, e.g., Int in Box<Int>() or a<Int>(). A type is not the same as a class. For a class User, there are at least two types: User and User?. For a generic class, there are many types, like Box<Int>, and Box<String>.