Kotlin 泛型中的 in 和 out

簡評:在 Kotlin 中使用泛型你會注意到其中引入了 in 和 out,對于不熟悉的開發(fā)者來說可能有點難以理解。從形式上講,這是一種定義逆變和協(xié)變的方式,這篇文章就來講講怎么來理解和記住它們。

in & out 怎么記?

Out (協(xié)變)

如果你的類是將泛型作為內(nèi)部方法的返回,那么可以用 out:

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

可以稱其為 production class/interface,因為其主要是產(chǎn)生(produce)指定泛型對象。因此,可以這樣來記:produce = output = out。

In(逆變)

如果你的類是將泛型對象作為函數(shù)的參數(shù),那么可以用 in:

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

可以稱其為 consumer class/interface,因為其主要是消費指定泛型對象。因此,可以這樣來記:consume = input = in。

Invariant(不變)

如果既將泛型作為函數(shù)參數(shù),又將泛型作為函數(shù)的輸出,那就既不用 in 或 out。

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

舉個例子

假設(shè)我們有一個漢堡(burger)對象,它是一種快餐,當然更是一種食物。

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

1. 漢堡提供者

根據(jù)上面定義的類和接口來設(shè)計提供 food, fastfoodburger 的類:

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

現(xiàn)在,我們可以這樣賦值:

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

很顯然,漢堡商店屬于是快餐商店,當然也屬于食品商店。

因此,對于 out 泛型,我們能夠?qū)⑹褂米宇惙盒偷膶ο筚x值給使用父類泛型的對象。

而如果像下面這樣反過來使用子類 - Burger 泛型,就會出現(xiàn)錯誤,因為快餐(fastfood)和食品(food)商店不僅僅提供漢堡(burger)。

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

2. 漢堡消費者

再讓我們根據(jù)上面的類和接口來定義漢堡消費者類:

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

現(xiàn)在,我們能夠?qū)?Everybody, ModernPeople 和 American 都指定給漢堡消費者(Consumer<Burger>):

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

很顯然這里美國的漢堡的消費者既是現(xiàn)代人,更是人類。

因此,對于 in 泛型,我們可以將使用父類泛型的對象賦值給使用子類泛型的對象。

同樣,如果這里反過來使用父類 - Food 泛型,就會報錯:

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

根據(jù)以上的內(nèi)容,我們還可以這樣來理解什么時候用 in 和 out:

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

英文原文:In and out type variant of Kotlin

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 8,140評論 12 51
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,896評論 18 399
  • 趙威后是個奇葩老太太,也是一個明白老太太。她在會見娘家人齊國使者的時候,先詢問百姓的情況,后詢問國君的情況。頗令使...
    二班班閱讀 284評論 0 0
  • 懷胎十月,終于等到“卸貨”這一刻!But,是不是生完寶寶就一身輕松了呢?當然不是,寶寶出生后,以下這4件事媽媽早做...
    第6通道閱讀 293評論 0 0
  • 一絲不掛的枝頭 先飛來了三只 又飛來了兩只 轉(zhuǎn)眼間 飛走了四只 還有一只 為什么 你還在這里
    行者D也閱讀 261評論 0 0

友情鏈接更多精彩內(nèi)容