當我們在 Kotlin 中定義泛型時,我們會發現它需要使用到 in
和 out
兩個關鍵字來定義。從形式上來講,這是一種定義「逆變」和「協變」的方式。
那啥叫逆變?啥叫協變?可以參考下維基百科的定義:協變與逆變
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。
參考資料: