Kiểu biến In và Out của Kotlin

Nếu bạn đã từng định nghĩa generic trong Kotlin, bạn sẽ để ý nhiều lần, nó sẽ đề xuất sử dụng từ khoá inout để định nghĩa generic. Nó khiến chúng ta bắt đầu bối rối khi sử dụng, vậy tại sao?

Đây chính là cách để định nghĩa contravariancecovariant.

In và Out

Out (covariant type)

Nếu class generic của bạn chỉ sử dụng kiểu generic cho output của function, thì out được sử dụng:

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

Chúng ta sẽ gọi nó là class/interface sản xuất (production), vì nó chủ yếu để sản xuất ra các kiểu generic. Do đó, rất đơn giản, có thể nhớ là:

produce = output = out.

In (contravariance type)

Nếu class generic chỉ sử dụng kiểu generic cho input của function, thì in được sử dụng:

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

Chúng ta gọi nó là class/interface tiêu thụ (consumer), vì nó chủ yếu tiêu thụ các kiểu generic. Do đó, rất đơn giản, có thể nhớ là:

consume = input = in.

Invariant Type

Trong trường hợp class generic sử dụng kiểu generic như cả input và output cho function của nó, thì cả inout sẽ không cần được sử dụng. Nó là invariant:

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

Tại sao sử dụng inout?

Bây giờ bạn đã có thể dễ dàng nhớ được khi nào sử dụng với inout. Vậy ý nghĩa của chúng là gì? Trước khi tìm hiểu điều đó, chúng ta hãy xem ví dụ sau xác định đối tượng của class burger. Nó là một fastFood, một kiểu của food. Sự phân cấp đơn giản như hình vẽ dưới đây:

open class Food
open class FastFood : Food() 
class Burger : FastFood()

Burger Production

Quan sát interface generic Production ở trên, hãy mở rộng chúng để sản xuất các đối tượng food, fastfoodburger như bên dưới:

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()
    }
}

Bây giờ chúng ta có thể thực hiện như sau:

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

Chúng ta có thể thấy cả burger hay fastFood production, cũng đều là food production. Vì vậy,

Đối với out generic, chúng ta có thể gán một class của subtype với một class của super-type

Nếu chúng ta thay đổi như dưới đây, nó sẽ xảy ra lỗi, bởi vì sản xuất foodfastFood không chỉ là burger:

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

Burger Consumer

Bây giờ, chúng ta hãy quan sát interface generic Consumer ở trên, mở rộng nó với các consumer food, fastfoodburger như sau:

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")
    }
}

Chúng ta có thể assign chúng như sau:

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

Do vậy,

Đối với in generic, chúng ta có thể gán một class của super-type với một class của subtype

Nếu chúng ta thay đổi như dưới đây, sẽ xảy ra lỗi:

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

Một cách khác để nhớ inout

SuperType có thể được gán cho SubType, sử dụng in SubType có thể được gán cho SuperType, sử dụng out