100

In and out type variant of Kotlin – AndroidPub

 6 years ago
source link: https://android.jlelse.eu/in-and-out-type-variant-of-kotlin-587e4fa2944c?gi=f0f824e25afa
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

In and out type variant of Kotlin

1*oF7NzO4m7Ukc7rA8fzM51Q.png

If you ever defined generic in Kotlin, you’ll notice many a times, it would propose to use the in or out keyword to define the generic. It puzzles me at a start on when which is used, and for what.

Formally, this is a way to define contravariance and covariant. It took me a while to learn about it. I’ll dive in here to explain what how I understand and memorize their different.

In & Out easily remembered

Out (covariant type)

If your generic class only use the generic type as output of it’s function/s, then out is used i.e.

interface Production<out T> {
fun produce(): T
}

I call it production class/interface, as it is mainly to produce output of the generic type. Hence very simple one could remember

produce = output = out.

In (contravariance type)

If your generic class only use the generic type as input of it’s function/s, then in is used i.e.

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

I call it consumer class/interface, as it is mainly consuming the generic type. Hence very simple one could remember

consume = input = in.

Invariant Type

In the event one generic class uses the generic type as input and output to it’s function, then no in or out is used. It is invariant.

interface ProductionConsumer<T> {
fun produce(): T
fun consume(item: T)
}

Why use In and Out?

Well, now you could easily remember when in and out is stated, what is their significance? Before we go there, let’s define burger class object. It is a fastFood, which is a type of food.

The simple class hierarchy as below.

1*D-Z_va45Kc0PWGJfpq7wHA.png
open class Food
open class FastFood : Food()
class Burger : FastFood()

Burger Production

Looking at the genericProduction interface defined above, let’s further expand them to produce food, fastfood and burger as below

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, lets have Food Production holders, we could assigned all of them to it

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

Either a burger or fastFood production, is still a food production. Hence

For 'out' generic, we could assign a class of subtype to class of super-type

If we change to below, it would error out, because food or fastFood is not just a burger production.

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

Burger Consumer

Now, looking at the genericConsumer interface defined above, let’s further expand them to consume food, fastfood and burger as below

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, lets have Burger Consumer holders, we could assigned all of them to it

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

Here, a burger consumer is an American, who is also part of ModernPeople, who is part of Everybody. Hence

For ‘in' generic, we could assign a class of super-type to class of subtype

If we change to below, it would error out, because consumer of Food although could be American or ModernPeople,it is not just American or just ModernPeople.

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

Another way to remember In and Out

Given the above, another to know when to use what is, for

SuperType could be assigned subtype, use IN

SubType could be assigned to SuperType, use OUT

1*rXI0hFY84ICHcPnJv3aMJQ.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK