When we define generics in Kotlin, we see that it needs to be defined using the in and out keywords. Formally, this is a way of defining “contravariant” and “covariant”.

What does that mean? What is covariant? See wikipedia for a definition: covariant and contravariant

How to remember in & out?

Out

If the generic class only takes the generic type as the return (output) of the function, then use out:

interface Production<out T> {
    fun produce(a): T
}
Copy the code

You can call it a production class/interface because it is primarily used to produce specified generic objects. Thus, we can simply remember:

produce = output = out

In (inverse)

If the generic class takes only the generic type as an input parameter (input) to the function, use in:

interface Consumer<in T> {
    fun consume(item: T)
}
Copy the code

You can call it a consumer class/interface because it is primarily used to consume specified generic objects. So we can simply remember this:

consume = input = in

Weyl (invariant)

If a generic class takes a generic type as both a function parameter and its output, then neither out nor in is used:

interface ProductionConsumer<T> {
    fun produce(a): T
    fun consume(item: T)
}
Copy the code

Why use in & out?

For example, let’s define a hamburger object, which is a fast food and also a food.

open class Food
open class FastFood : Food(a)class Burger : FastFood(a)Copy the code

Burger maker

Based on the Production interface defined above, we can further extend them to produce food, fast food, and burgers:

class FoodStore : Production<Food> {
    override fun produce(a): Food {
        println("Produce food")
        return Food()
    }
}

class FastFoodStore : Production<FastFood> {
    override fun produce(a): FastFood {
        println("Produce fast food")
        return FastFood()
    }
}

class InOutBurger : Production<Burger> {
    override fun produce(a): Burger {
        println("Produce burger")
        return Burger()
    }
}
Copy the code

Now, we can assign:

val production1 : Production<Food> = FoodStore()
val production2 : Production<Food> = FastFoodStore()
val production3 : Production<Food> = InOutBurger()
Copy the code

Obviously, burger shops are fast food stores as well as food stores.

Thus, for the OUT type, we can assign objects that use subclass generics to objects that use superclass generics.

If we modify it to the following, then we will make an error, because food or fast food stores can produce burgers, but not necessarily only burgers:

val production1 : Production<Burger> = FoodStore()  // Error
val production2 : Production<Burger> = FastFoodStore()  // Error
val production3 : Production<Burger> = InOutBurger()
Copy the code

Burger consumer

With the Consumer interface defined above, we can further extend them to consume food, fast food, and burgers:

class Everybody : Consumer<Food> {
    override fun consume(item: Food) {
        println("Eat food")}}class ModernPeople : Consumer<FastFood> {
    override fun consume(item: FastFood) {
        println("Eat fast food")}}class American : Consumer<Burger> {
    override fun consume(item: Burger) {
        println("Eat burger")}}Copy the code

We can designate humans, modern humans, and Americans as burger consumers, so we can assign:

val consumer1 : Consumer<Burger> = Everybody()
val consumer2 : Consumer<Burger> = ModernPeople()
val consumer3 : Consumer<Burger> = American()
Copy the code

Understandably, burger consumers can be American, modern and even human.

Therefore, for in generics, we can assign objects that use parent generics to objects that use subclass generics.

On the other hand, if we modify it as follows, there will be an error, because burger consumers are not just Americans or modern people.

val consumer1 : Consumer<Food> = Everybody()
val consumer2 : Consumer<Food> = ModernPeople()  // Error
val consumer3 : Consumer<Food> = American()  // Error
Copy the code

Remember another way of saying in & out

  • A parent generic object can be assigned to a child generic object using in;
  • Subclass generic objects can be assigned to superclass generic objects, using out.

References:

In and out type variant of Kotlin

In and out in Kotlin generics