Kotlin 泛型中的 in 和 out

zhich發表於2019-03-27

當我們在 Kotlin 中定義泛型時,我們會發現它需要使用到 inout 兩個關鍵字來定義。從形式上來講,這是一種定義「逆變」和「協變」的方式。

那啥叫逆變?啥叫協變?可以參考下維基百科的定義:協變與逆變

in & out 怎麼記?

out(協變)

如果泛型類只將泛型型別作為函式的返回(輸出),那麼使用 out:

interface Production<out T> {
    fun produce(): T
}
複製程式碼

可以稱之為生產類/介面,因為它主要是用來生產(produce)指定的泛型物件。因此,我們可以簡單地這樣記憶:

produce = output = out

in(逆變)

如果泛型類只將泛型型別作為函式的入參(輸入),那麼使用 in:

interface Consumer<in T> {
    fun consume(item: T)
}
複製程式碼

可以稱之為消費者類/介面,因為它主要是用來消費(consume)指定的泛型物件。因此我們可以簡單地這樣記憶:

consume = input = in

invariant(不變)

如果泛型類既將泛型型別作為函式引數,又將泛型型別作為函式的輸出,那麼既不用 out 也不用 in:

interface ProductionConsumer<T> {
    fun produce(): T
    fun consume(item: T)
}
複製程式碼

為啥要使用 in & out ?

舉個例子,我們定義下漢堡類物件,它是一種快餐,也是一種食物。

open class Food
open class FastFood : Food() 
class Burger : FastFood()
複製程式碼

漢堡生產者

根據上面定義的生產(Production)介面,我們可以進一步擴充套件它們來生產食物、快餐和漢堡:

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()
    }
}
複製程式碼

現在,我們可以這樣賦值:

val production1 : Production<Food> = FoodStore()
val production2 : Production<Food> = FastFoodStore()
val production3 : Production<Food> = InOutBurger()
複製程式碼

顯然,漢堡商店屬於快餐商店,也屬於食物商店。

因此,對於 out 型別,我們能夠將使用子類泛型的物件賦值給使用父類泛型的物件。

如果我們修改如下,那麼就會出錯了,因為食物或快餐商店是可以生產漢堡,但不一定僅僅生產漢堡:

val production1 : Production<Burger> = FoodStore()  // Error
val production2 : Production<Burger> = FastFoodStore()  // Error
val production3 : Production<Burger> = InOutBurger()
複製程式碼

漢堡消費者

根據上面定義的消費(Consumer)介面,我們可以進一步擴充套件它們來消費食物、快餐和漢堡:

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")
    }
}
複製程式碼

我們可以將人類、現代人、美國人指定為漢堡消費者,所以可以這樣賦值:

val consumer1 : Consumer<Burger> = Everybody()
val consumer2 : Consumer<Burger> = ModernPeople()
val consumer3 : Consumer<Burger> = American()
複製程式碼

不難理解,漢堡的消費者可以是美國人,也可以是現代人,更可以是人類。

因此,對於 in 泛型,我們能夠將使用父類泛型的物件賦值給使用子類泛型的物件。

反之,如果我們修改如下,就會出現錯誤,因為漢堡的消費者不僅僅是美國人或現代人。

val consumer1 : Consumer<Food> = Everybody()
val consumer2 : Consumer<Food> = ModernPeople()  // Error
val consumer3 : Consumer<Food> = American()  // Error
複製程式碼

記住 in & out 的另一種方式

  • 父類泛型物件可以賦值給子類泛型物件,用 in;
  • 子類泛型物件可以賦值給父類泛型物件,用 out。

參考資料:

In and out type variant of Kotlin

Kotlin 泛型中的 in 和 out

相關文章