Kotlin 范型之協(xié)變、逆變

花和草.jpg

一. 類(lèi)(Class) 與類(lèi)型(Type)

Kotlin 中類(lèi)和類(lèi)型是不一樣的概念陨享。

下圖充分展示了它們的區(qū)別葱淳。

class vs type.png

二. 型變

型變是指類(lèi)型轉(zhuǎn)換后的繼承關(guān)系。

Kotlin 的型變分為逆變抛姑、協(xié)變和不變赞厕。

2.1 協(xié)變

如果 A 是 B 的子類(lèi)型,并且Generic<A> 也是 Generic<B> 的子類(lèi)型定硝,那么 Generic<T> 可以稱(chēng)之為一個(gè)協(xié)變類(lèi)皿桑。

2.1.1 Java 上界通配符<? extends T>

Java 的協(xié)變通過(guò)上界通配符實(shí)現(xiàn)。

如果 Dog 是 Animal 的子類(lèi)蔬啡,但 List<Dog> 并不是 List<Animal> 的子類(lèi)诲侮。
下面的代碼會(huì)在編譯時(shí)報(bào)錯(cuò):

        List<Animal> animals = new ArrayList<>();
        List<Dog> dogs = new ArrayList<>();
        animals = dogs; // incompatible types

而使用上界通配符之后,List<Dog> 變成了 List<? extends Animal> 的子類(lèi)型箱蟆。即 animals 變成了可以放入任何 Animal 及其子類(lèi)的 List沟绪。

因此,下面的代碼編譯是正確的:

        List<? extends Animal> animals = new ArrayList<>();
        List<Dog> dogs = new ArrayList<>();
        animals = dogs;

2.1.2 Kotlin 的關(guān)鍵詞 out

上述代碼改成 Kotlin 的代碼:

fun main() {

    var animals: List<Animal> = ArrayList()
    val dogs = ArrayList<Dog>()
    animals = dogs
}

居然沒(méi)有編譯報(bào)錯(cuò)空猜?其實(shí)绽慈,Kotlin 的 List 跟 Java 的 List 并不一樣恨旱。

Kotlin 的 List 源碼中使用了outout相當(dāng)于 Java 上界通配符坝疼。

public interface List<out E> : Collection<E> {

    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator<E>

    override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean

    public operator fun get(index: Int): E

    public fun indexOf(element: @UnsafeVariance E): Int

    public fun lastIndexOf(element: @UnsafeVariance E): Int

    public fun listIterator(): ListIterator<E>

    public fun listIterator(index: Int): ListIterator<E>

    public fun subList(fromIndex: Int, toIndex: Int): List<E>
}

類(lèi)的參數(shù)類(lèi)型使用了out之后搜贤,該參數(shù)只能出現(xiàn)在方法的返回類(lèi)型。

2.1.3 @UnsafeVariance

但是裙士,Kotlin List 的 contains入客、containsAll、indexOf 和 lastIndexOf 方法中腿椎,入?yún)⒕霈F(xiàn)了范型 E桌硫。并且使用 @UnsafeVariance 修飾。

正是由于 @UnsafeVariance 的修飾啃炸,打破了剛才的限制铆隘,否則會(huì)編譯報(bào)錯(cuò)。

2.2 逆變

如果 A 是 B 的子類(lèi)型南用,并且 Generic<B> 是 Generic<A> 的子類(lèi)型膀钠,那么 Generic<T> 可以稱(chēng)之為一個(gè)逆變類(lèi)。

2.2.1 Java 下界通配符<? super T>

Java 的逆變通過(guò)下界通配符實(shí)現(xiàn)肿嘲。

下面的代碼因?yàn)槭菂f(xié)變的,無(wú)法添加新的對(duì)象筑公。編譯器只能知道類(lèi)型是 Animal 的子類(lèi),并不能確定具體類(lèi)型是什么匣屡,因此無(wú)法驗(yàn)證類(lèi)型的安全性。

        List<? extends Animal> animals = new ArrayList<>();
        animals.add(new Dog()); // compile error

使用下界通配符之后捣作,代碼編譯通過(guò):

        List<? super Animal> animals = new ArrayList<>();
        animals.add(new Dog());

? super Animal 表示 Animal 及其父類(lèi) 。所以 animals 可以接收所有 Animal 的子類(lèi)添加至該列表中券躁。

Java 的上界通配符和下界通配符符合 PECS 原則惩坑。

PECS 原則即 Producer Extends,Consumer Super 也拜。如果參數(shù)化類(lèi)型是一個(gè)生產(chǎn)者旭贬,則使用 <? extends T>;如果它是一個(gè)消費(fèi)者搪泳,則使用 <? super T>浑吟。

其中贺纲,生產(chǎn)者表示頻繁往外讀取數(shù)據(jù) T,而不從中添加數(shù)據(jù)备典。消費(fèi)者表示只往里插入數(shù)據(jù) T,而不讀取數(shù)據(jù)艰赞。

2.2.2 Kotlin 的關(guān)鍵詞 in

in相當(dāng)于 Java 下界通配符佣谐。

abstract class Printer<in E> {

    abstract fun print(value: E): Unit
}

class AnimalPrinter: Printer<Animal>() {

    override fun print(animal: Animal) {
        println("this is animal")
    }
}

class DogPrinter : Printer<Dog>() {

    override fun print(dog: Dog) {
        println("this is dog")
    }
}

fun main() {

    val animalPrinter = AnimalPrinter()
    animalPrinter.print(Animal())

    val dogPrinter = DogPrinter()
    dogPrinter.print(Dog())
}

類(lèi)的參數(shù)類(lèi)型使用了in之后,該參數(shù)只能出現(xiàn)在方法的入?yún)ⅰ?/p>

2.3 不變

默認(rèn)情況下方妖,Kotlin 中的泛型類(lèi)是不變的狭魂。 這意味著它們既不是協(xié)變的也不是逆變的。

例如 MutableList党觅,它可讀可寫(xiě)雌澄,泛型沒(méi)有使用inout杯瞻。

三. 總結(jié)

本文從 Kotlin 的類(lèi)镐牺、類(lèi)型引出了型變。介紹了 Kotlin 的協(xié)變魁莉、協(xié)變和不變的概念和特性睬涧,以及 Java 的上界通配符、下界通配符旗唁。

該系列的相關(guān)文章:
Kotlin 泛型之類(lèi)型擦除

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畦浓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子检疫,更是在濱河造成了極大的恐慌讶请,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件电谣,死亡現(xiàn)場(chǎng)離奇詭異秽梅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)剿牺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)企垦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人晒来,你說(shuō)我怎么就攤上這事钞诡。” “怎么了湃崩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵荧降,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我攒读,道長(zhǎng),這世上最難降的妖魔是什么薄扁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮脱盲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掖看。我一直安慰自己面哥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布耳峦。 她就那樣靜靜地躺著蹲坷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪循签。 梳的紋絲不亂的頭發(fā)上疙咸,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天撒轮,我揣著相機(jī)與錄音,去河邊找鬼题山。 笑死,一個(gè)胖子當(dāng)著我的面吹牛顶瞳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播慨菱,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼符喝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了协饲?” 一聲冷哼從身側(cè)響起课蔬,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎流昏,沒(méi)想到半個(gè)月后吞获,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刁绒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年知市,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫂丙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跟啤,死狀恐怖隅肥,靈堂內(nèi)的尸體忽然破棺而出袄简,到底是詐尸還是另有隱情,我是刑警寧澤痘番,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布汞舱,位于F島的核電站,受9級(jí)特大地震影響莹规,放射性物質(zhì)發(fā)生泄漏泌神。R本人自食惡果不足惜舞虱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一矾兜、第九天 我趴在偏房一處隱蔽的房頂上張望患久。 院中可真熱鬧,春花似錦返帕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)算行。三九已至,卻和暖如春州邢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背量淌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工呀枢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裙秋。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓摘刑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親党晋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分舞终,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 7,795評(píng)論 12 51
  • 泛型 泛型(Generic Type)簡(jiǎn)介 通常情況的類(lèi)和函數(shù),我們只需要使用具體的類(lèi)型即可:要么是基本類(lèi)型上陕,要么...
    Tenderness4閱讀 1,412評(píng)論 4 2
  • 有一次 他帶我去跟他的好朋友一起吃海鮮大餐 在我們的旁邊有臺(tái)夾娃娃機(jī) 我興奮的拉著他的手說(shuō)要去夾娃娃 他瞬間從口袋...
    萌噠噠嘛閱讀 272評(píng)論 0 0
  • 見(jiàn):想到父親就讓我回憶起很久之前一個(gè)很幽默的啤酒廣告释簿。廣告里有個(gè)穿著時(shí)髦的男人庶溶,坐在桌子旁邊懂鸵,聚精會(huì)神地凝望這手中...
    huifang963閱讀 196評(píng)論 0 0