Short comment: When using generics in Kotlin you will notice the introduction of in and out, which may be a bit confusing for unfamiliar developers. Formally, this is a way of defining contravariance and covariance , and this article will explain how to understand and remember them.
How to remember in & out?
Out (covariant)
If your class returns generics as internal methods, you can use out:
interface Production<out T> {
fun produce(): T
}
It can be called a production class/interface because it mainly produces the specified generic object. Therefore, it can be written like this: produce = output = out .
In (inverted)
If your class takes generic objects as function parameters, you can use in:
interface Consumer<in T> {
fun consume(item: T)
}
It can be called consumer class/interface because it mainly consumes specified generic objects. Therefore, it can be written like this: consume = input = in.
Invariant (unchanged)
If you have generics as function parameters and generics as the output of the function, then neither in nor out is used.
interface ProductionConsumer<T> {
fun produce(): T
fun consume(item: T)
}
for example
Suppose we have a burger object, which is a fast food and of course a food.
open class Foodopen class FastFood : Food() class Burger : FastFood()
1. Burger Provider
Design the classes that provide food, fastfood and burger according to the classes and interfaces defined above :
class FoodStore : Production<Food> {
override fun produce(): Food {
println("Produce food")
return Food()
}
}class FastFoodStore : Production<FastFood> {
override fun produce(): FastFood {
println("Produce food")
return FastFood()
}
}class InOutBurger : Production<Burger> {
override fun produce(): Burger {
println("Produce burger")
return Burger()
}
}
Now, we can assign values like this:
val production1 : Production<Food> = FoodStore()
val production2 : Production<Food> = FastFoodStore()
val production3 : Production<Food> = InOutBurger()
Obviously, a burger store is a fast food store, and of course a food store.
So, with out generics, we can assign objects that use subclass generics to objects that use superclass generics.
And if you use the subclass - Burger generic in reverse like below, you will get an error because fastfood and food stores don't just serve burgers.
val production1 : Production<Burger> = FoodStore() // Error
val production2 : Production<Burger> = FastFoodStore() // Error
val production3 : Production<Burger> = InOutBurger()
2. Burger consumers
Let's define the hamburger consumer class based on the above class and interface:
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")
}
}
现在,我们能够将 Everybody, ModernPeople 和 American 都指定给汉堡消费者(Consumer<Burger>):
val consumer1 : Consumer<Burger> = Everybody()
val consumer2 : Consumer<Burger> = ModernPeople()
val consumer3 : Consumer<Burger> = American()
很显然这里美国的汉堡的消费者既是现代人,更是人类。
Therefore, for in generics, we can assign an object that uses the superclass generic to an object that uses the subclass generic.
Similarly, if the parent class - Food generic is used in reverse here, an error will be reported:
val consumer1 : Consumer<Food> = Everybody()
val consumer2 : Consumer<Food> = ModernPeople() // Error
val consumer3 : Consumer<Food> = American() // Error
Based on the above, we can also understand when to use in and out in this way:
The parent class generic object can be assigned to the subclass generic object, use in;
Subclass generic objects can be assigned to superclass generic objects with out.
English original : In and out type variant of Kotlin