Introduction
If you’ve ever defined generic in Kotlin, you’ll notice a lot of times, it will suggest using the in or out keyword to define generic. It confuses me at the beginning of when, what is used, and what is used for. Officially, this is a way to define contravariance and covariant (Reverse and copy variants). It takes a while to learn about it. I will drill down to explain what I understand and remember their differences.
In & Out easily remembered
Out (covariant type)
If your generic class only uses generic as its function output, out is used as the following example:
1 2 3 4 | interface Production<out T> { fun produce(): T } |
I call the production class / interface, because its main task is to produce generic types. Therefore very simple to remember:
produce = output = out. |
---|
Print (contravariance tye)
If your generic class only uses the generic type as its function input, print is used as the following example:
1 2 3 4 | interface Consumer<in T> { fun consume(item: T) } |
I call lnos class / interface consumption, because its main task is to consume generic type. It is therefore very simple that one can remember:
consuming = input = in. |
---|
Invariant type (Invariant variant)
In the case of a generic class using the generic type as its input and output functions, no in or out is used. It is an immutable variant.
1 2 3 4 5 | interface ProductionConsumer<T> { fun produce(): T fun consume(item: T) } |
Why use In and Out?
Okay, now you can easily remember when print and out were mentioned, what do they mean? Before we continue, let’s define the burger class object. It is a fastfood, which is a type of food. The class structure is as simple as below.
1 2 3 4 | open class Food open class FastFood : Food() class Burger : FastFood() |
Burger Production
Looking at the Production Generic Interface defined above, expand them to a foot, fastfood, and burger provider as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class FoodStore : Production<Food> { override fun produce(): Food { println("Produce food") return Food() } } class FastFoodStore : Production<FastFood> { override fun produce(): FastFood { println("Produce fast food") return FastFood() } } class InOutBurger : Production<Burger> { override fun produce(): Burger { println("Produce burger") return Burger() } } |
Now let’s create Food Production holders, we can give them all.
1 2 3 4 | val production1 : Production<Food> = FoodStore() val production2 : Production<Food> = FastFoodStore() val production3 : Production<Food> = InOutBurger() |
Both a burger or fastFood production is a food production. Therefore
For generic ‘out’, we can assign a subtype class to super-type class. |
---|
If we change it as below, it will cause an error, because food and fastFood are not just a burger.
1 2 3 4 | val production1 : Production<Burger> = FoodStore() // Error val production2 : Production<Burger> = FastFoodStore() // Error val production3 : Production<Burger> = InOutBurger() |
Burger Consumer
Now, see the Consumer generic interface defined above. Expand them further to consume food, fast food, and burgers as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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") } } |
Now, let’s create Burger Consumer holders, we can assign all of them to it.
1 2 3 4 | val consumer1 : Consumer<Burger> = Everybody() val consumer2 : Consumer<Burger> = ModernPeople() val consumer3 : Consumer<Burger> = American() |
Here, a consumer burger is an American, who is also part of ModernPeople, or possibly part of Everybody. Therefore:
For generic ‘in’, we can assign a super-type class to a subtype class. |
---|
If we change it below, it will produce an error, because Food’s consumer may be American or ModernPeople but it’s not just American or just ModernPeople.
1 2 3 4 | val consumer1 : Consumer<Food> = Everybody() val consumer2 : Consumer<Food> = ModernPeople() // Error val consumer3 : Consumer<Food> = American() // Error |
Another way to remember In and Out
Starting from the above, another way to know when to use something for:
* SuperType can be assigned as subtype, using IN * Subtype can be assigned to SuperType, using OUT |
---|
I hope you understand this article and it’s helpful for you. Please share it with others.
Source
https://medium.com/@elye.project/in-and-out-type-variant-of-kotlin-587e4fa2944c
Reference
https://medium.com/@elye.project/
VII. P / S
If my viblo posts have a Source , this is a translation from the source that is linked to the original post in this section. These are the articles I selected + searched + synthesized from Google in the process of dealing with issues when doing practical projects + useful and interesting for myself. => Translate as an article to rummage through when needed. So when reading the article, please note: