第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

Kotlin 泛型型變

上篇文章我們一起為 Kotlin 中的泛型型變做了一個(gè)很好的鋪墊,深入分析下類型和類,子類型和子類之間的關(guān)系、什么是子類型化關(guān)系以及型變存在的意義。那么今天將會(huì)講點(diǎn)更有挑戰(zhàn)性的東西,也就是 Kotlin 泛型型變中最為難理解的地方,那就是 Kotlin 中的協(xié)變、逆變、不變。

1. 泛型協(xié)變 (保留子類型化關(guān)系)

1.1 協(xié)變基本定義和介紹

還記得上篇的子類型化關(guān)系嗎?協(xié)變實(shí)際上就是保留子類型化關(guān)系,首先我們需要去明確一下這里所說(shuō)的保留子類型化關(guān)系是針對(duì)誰(shuí)而言的呢?

基本介紹

來(lái)看個(gè)例子,StringString? 的子類型,我們知道基礎(chǔ)類型 List<out E> 是協(xié)變的,那么 List<String> 也就是 List<String?> 的子類型的。很明顯這里針對(duì)的角色就是 List<String>List<String?>, 是它們保留了 StringString? 的子類型化關(guān)系?;蛘邠Q句話說(shuō)兩個(gè)具有相同的基礎(chǔ)類型的泛型協(xié)變類型,如果類型實(shí)參具有子類型化關(guān)系,那么這個(gè)泛型類型具有一致方向的子類型化關(guān)系。那么具有子類型化關(guān)系實(shí)際上子類型的值能在任何時(shí)候任何地方替代超類型的值。

基本定義

interface Producer<out T> {//在泛型類型形參前面指定out修飾符
   val something: T
   fun produce(): T
}

1.2 什么是 out 協(xié)變點(diǎn)

從上面定義的基本結(jié)構(gòu)來(lái)看,實(shí)際上協(xié)變點(diǎn)就是上面 produce 函數(shù)返回值的 T 的位置,Kotlin 中規(guī)定一個(gè)泛型協(xié)變類,在泛型形參前面加上 out 修飾后,那么修飾這個(gè)泛型形參在函數(shù)內(nèi)部使用范圍將受到限制只能作為函數(shù)的返回值或者修飾只讀權(quán)限的屬性。

interface Producer<out T> {//在泛型類型形參前面指定out修飾符
   val something: T//T作為只讀屬性的類型,這里T的位置也是out協(xié)變點(diǎn)
   fun produce(): T//T作為函數(shù)的返回值輸出給外部,這里T的位置就是out協(xié)變點(diǎn)
}

以上協(xié)變點(diǎn)都是標(biāo)準(zhǔn)的 T 類型,實(shí)際上以下這種方式其實(shí)也是協(xié)變點(diǎn),請(qǐng)注意體會(huì)協(xié)變點(diǎn)含義:

interface Producer<out T> {
   val something: List<T>//即使T不是單個(gè)的類型,但是它作為一個(gè)泛型類型修飾只讀屬性,所以它所處位置還是out協(xié)變點(diǎn)
   
   fun produce(): List<Map<String,T>>//即使T不是單個(gè)的類型,但是它作為泛型類型的類型實(shí)參修飾返回值,所以它所處位置還是out協(xié)變點(diǎn)
}

1.3 out 協(xié)變點(diǎn)基本特征

協(xié)變點(diǎn)基本特征: 如果一個(gè)泛型類聲明成協(xié)變的,用 out 修飾的那個(gè)類型形參,在函數(shù)內(nèi)部出現(xiàn)的位置只能在只讀屬性的類型或者函數(shù)的返回值類型。相對(duì)于外部而言協(xié)變是生產(chǎn)泛型參數(shù)的角色,生產(chǎn)者向外輸出 out

1.4 協(xié)變 -List<out E> 的源碼分析

我們?cè)谏掀恼轮芯驼f(shuō)過(guò) Kotlin 中的 List 并不是 Java 中的 List, 因?yàn)?Kotlin 中的 List 是個(gè)只讀的 List 不具備修改集合中元素的操作方法。Java 的 List 實(shí)際上相當(dāng)于 Kotlin 中的 MutableList 具有各種讀和寫的操作方法。

Kotlin 中的 List<out E> 實(shí)際上就是協(xié)變的例子,用它來(lái)說(shuō)明分析協(xié)變最好不過(guò)了,還記得上篇文章說(shuō)過(guò)的學(xué)習(xí)泛型步驟二嗎,就是通過(guò)分析源碼來(lái)驗(yàn)證自己的理解和結(jié)論。通過(guò)以下源碼均可驗(yàn)證我們上述所說(shuō)的結(jié)論。

//通過(guò)泛型類定義可以看出使用out修飾符 修飾泛型類型形參E
public interface List<out E> : Collection<E> {
    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean//咦! 咦! 咦! 和說(shuō)的不一樣啊,為什么還能出現(xiàn)在這個(gè)位置,還出來(lái)了個(gè)@UnsafeVariance 這個(gè)是什么鬼? 告訴你,穩(wěn)住,先不要急,請(qǐng)聽(tīng)我在后面慢慢說(shuō)來(lái),先暫時(shí)保留神秘感
    override fun iterator(): Iterator<E>//這里明顯能看出來(lái)E處于out協(xié)變點(diǎn)位置,而且還是泛型類型Iterator<E>出現(xiàn)的,正好驗(yàn)證我們上述所說(shuō)的協(xié)變的變種類型(E為類型實(shí)參的泛型類型)

    override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
    public operator fun get(index: Int): E//函數(shù)返回值的類型E,這里明顯能看出來(lái)E處于out協(xié)變點(diǎn)位置,正好驗(yàn)證我們上述所說(shuō)的協(xié)變的標(biāo)準(zhǔn)類型(E直接為返回值的類型)
    public fun indexOf(element: @UnsafeVariance E): Int

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

    public fun listIterator(): ListIterator<E>//(E為類型實(shí)參的泛型類型),為out協(xié)變點(diǎn)

    public fun listIterator(index: Int): ListIterator<E>//(E為類型實(shí)參的泛型類型),為out協(xié)變點(diǎn)
    public fun subList(fromIndex: Int, toIndex: Int): List<E>//(E為類型實(shí)參的泛型類型),為out協(xié)變點(diǎn)
}

源碼分析完了,是不是感覺(jué)還是有點(diǎn)迷惑?。烤褪?E 為啥還能在其他的位置上,還有 @UnsafeVariance 是個(gè)什么東西呢?這些疑問(wèn)先放一放,但是上述至少證明了泛型協(xié)變 out 協(xié)變的位置是返回值的類型以及只讀屬性的類型 (這點(diǎn)源碼中沒(méi)有表現(xiàn)出來(lái),但是實(shí)際上卻是如此啊,這里可以自行查閱其他例子)。

2. 泛型逆變 (反轉(zhuǎn)子類型化關(guān)系)

2.1 逆變基本定義和介紹

基本介紹

逆變實(shí)際上就是和協(xié)變子類型化關(guān)系正好相反,它是反轉(zhuǎn)子類型化關(guān)系。

來(lái)個(gè)例子說(shuō)明下,我們知道 StringString? 的子類型,Comparable<in T> 是逆變的,那么 Comparable<String>Comparable<String?> 實(shí)際上是反轉(zhuǎn)了 StringString? 的子類型化關(guān)系,也就是和 StringString? 的子類型化關(guān)系相反,那么 Comparable<String?> 就是 Comparable<String> 子類型,Comparable<String> 類型值出現(xiàn)的地方都可用 Comparable<String?> 類型值來(lái)替代。

換句話說(shuō)就是: 兩個(gè)具有相同的基礎(chǔ)類型的泛型逆變類型,如果類型實(shí)參具有子類型化關(guān)系,那么這個(gè)泛型類型具有相反方向的子類型化關(guān)系

  • 基本定義
interface Consumer<in T>{//在泛型類型形參前面指定in修飾符
   fun consume(value: T)
}

2.2 什么是 in 逆變點(diǎn)

從上面定義的基本結(jié)構(gòu)來(lái)看,實(shí)際上逆變點(diǎn)就是上面 consume 函數(shù)接收函數(shù)形參的 T 的位置,Kotlin 中規(guī)定一個(gè)泛型協(xié)變類,在泛型形參前面加上 out 修飾后,那么修飾這個(gè)泛型形參在函數(shù)內(nèi)部使用范圍將受到限制只能作為函數(shù)的返回值或者修飾只讀權(quán)限的屬性。

interface Consumer<in T>{//在泛型類型形參前面指定in修飾符
   var something: T //T作為可變屬性的類型,這里T的位置也是in逆變點(diǎn)
   fun consume(value: T)//T作為函數(shù)形參類型,這里T的位置也就是in逆變點(diǎn)
}

和協(xié)變類似,逆變也存在那種泛型類型處于逆變點(diǎn)的位置,這些我們都可以把當(dāng)做逆變點(diǎn):

interface Consumer<in T>{
   var something: B<T>//這里雖然是泛型類型但是T所在位置依然是修飾可變屬性類型,所以仍處于逆變點(diǎn)
   fun consume(value: A<T>)//這里雖然是泛型類型但是T所在位置依然是函數(shù)形參類型,所以仍處于逆變點(diǎn)
}

2.3 in 逆變點(diǎn)基本特征

逆變點(diǎn)基本特征: 如果一個(gè)泛型類聲明成逆變的,用 in 修飾泛型類的類型形參,在函數(shù)內(nèi)部出現(xiàn)的位置只能是作為可變屬性的類型或者函數(shù)的形參類型。相對(duì)于外部而言逆變是消費(fèi)泛型參數(shù)的角色,消費(fèi)者請(qǐng)求外部輸入 in。

2.4 逆變 -Comparable<in T> 的源碼分析

在 Kotlin 中其實(shí)最簡(jiǎn)單的泛型逆變的例子就是 Comparable<in T>

public interface Comparable<in T> {//泛型逆變使用in關(guān)鍵字修飾
    /**
     * Compares this object with the specified object for order. Returns zero if this object is equal
     * to the specified [other] object, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    public operator fun compareTo(other: T): Int//因?yàn)槭悄孀兊模訲在函數(shù)內(nèi)部出現(xiàn)的位置作為compareTo函數(shù)的形參類型,可以看出它是屬于消費(fèi)泛型參數(shù)的
}

3. 泛型不變 - 無(wú)子類型化關(guān)系

3.1 不變基本定義和介紹

基本介紹

對(duì)于不變就更簡(jiǎn)單了,泛型型變中除去協(xié)變、逆變就是不變了。其實(shí)不變看起來(lái)就是我們常用的普通泛型,它既沒(méi)有 in 關(guān)鍵字修飾,也沒(méi)有 out 關(guān)鍵字修飾。它就是普通的泛型,所以很明顯它沒(méi)有像協(xié)變、逆變那樣那么多的條條框框,它很自由既可讀又可寫,既可以作為函數(shù)的返回值類型也可以作為函數(shù)形參類型,既可以聲明成只讀屬性的類型又可以聲明可變屬性。

但是注意了:不變型就是沒(méi)有子類型化關(guān)系,所以它會(huì)有一個(gè)局限性就是如果以它作為函數(shù)形參類型,外部傳入只能是和它相同的類型,因?yàn)樗揪筒淮嬖谧宇愋突P(guān)系說(shuō)法,那也就是沒(méi)有任何類型值能夠替換它,除了它自己本身的類型 例如 MutableList<String>和MutableList<String?> 是完全兩種不一樣的類型,盡管 StringString? 子類型,但是基礎(chǔ)泛型 MutableList<E> 是不變型的,所以 MutableList<String>和MutableList<String?> 根本沒(méi)關(guān)系。

基本定義


interface MutableList<E>{//沒(méi)有in和out修飾
   fun add(element: E)//E可以作為函數(shù)形參類型處于逆變點(diǎn),輸入消費(fèi)E
   fun subList(fromIndex: Int, toIndex: Int): MutableList<E>//E又可以作為函數(shù)返回值類型處于協(xié)變點(diǎn),生產(chǎn)輸出E
}

4. 協(xié)變、逆變、不變的規(guī)則引出幾個(gè)問(wèn)題

思考 1:協(xié)變泛型類的泛型形參類型 T 一定就只能 out 協(xié)變點(diǎn)位置嗎?能不能在 in 逆變點(diǎn)位置呢?

解答 1:可以在逆變點(diǎn),但是必須在函數(shù)內(nèi)部保證該泛型參數(shù) T 不存在寫操作行為,只能有讀操作
出現(xiàn)的場(chǎng)景:

聲明了協(xié)變的泛型類,但是有時(shí)候需要從外部傳入一個(gè)該類型形參的函數(shù)參數(shù),那么這個(gè)形參類型就處于 in 逆變點(diǎn)的位置了,但是函數(shù)內(nèi)部能夠保證不會(huì)對(duì)泛型參數(shù)存在寫操作的行為。常見(jiàn)例子就是 List<out E> 源碼,就是上面大家一臉懵逼的地方,就是那個(gè)為什么定義成協(xié)變的泛型 T 跑到了函數(shù)形參類型上去。 如下面部分代碼所示:

  override fun contains(element: @UnsafeVariance E): Boolean//咦! 咦! 咦! 和說(shuō)的不一樣啊,為什么還能出現(xiàn)在這個(gè)位置,還出來(lái)了個(gè)@UnsafeVariance 這個(gè)是什么鬼? 現(xiàn)在回答你就是可能會(huì)出現(xiàn)在這,但是只要保證函數(shù)不會(huì)寫操作即可

上述的 List 中的 contains 函數(shù)形參就是泛型形參 E,它是協(xié)變的出現(xiàn)在逆變點(diǎn),但是只要保證函數(shù)內(nèi)部不會(huì)對(duì)它有寫操作即可。

思考 2:逆變泛型類的泛型形參類型 T 就一定只能在 in 逆變點(diǎn)位置嗎?能不能在 out 協(xié)變點(diǎn)位置呢?
解答 2:同理,也可以在協(xié)變點(diǎn)位置。

思答 3:能在其他的位置嗎?比如構(gòu)造函數(shù)?
解答 3:可以在構(gòu)造器函數(shù)中,因?yàn)檫@是個(gè)比較特殊的位置,既不在 in 位置也不在 out 位置。


class ClassMates<out T: Student>(vararg students: T){//可以看到雖然定義成了協(xié)變,但是這里的T不是在out協(xié)變點(diǎn)的位置,這種聲明依然是合法的
   ...
}

注意: 這里就是很特殊的場(chǎng)景了,所以開(kāi)頭就說(shuō)過(guò)了如果把這些規(guī)則,用法只是死記硬背下來(lái),碰到這種場(chǎng)景的時(shí)候就開(kāi)始懷疑人生了,規(guī)則中不是這樣的啊,規(guī)則中定義協(xié)變點(diǎn)就是只讀屬性類型和函數(shù)返回值類型的位置啊,這個(gè)位置不上不下的該怎么解釋呢?

所以解決問(wèn)題還是需要抓住問(wèn)題的關(guān)鍵才是最主要的。其實(shí)解釋這個(gè)問(wèn)題也不難,回到型變的目的和初衷上去,型變是為了解決類型安全問(wèn)題,是防止更加泛化的實(shí)例調(diào)用某些存在危險(xiǎn)操作的方法。構(gòu)造函數(shù)很特殊一般創(chuàng)建后實(shí)例對(duì)象后,在該對(duì)象基礎(chǔ)上構(gòu)造函數(shù)是不能再被調(diào)用的,所以這里 T 放在這里是安全的。

思考 4:為了安全,我是不是只要把所有泛型類全都定義成協(xié)變或逆變或不變一種就可以了呢?

解答 4:不行,這樣不安全,按照實(shí)際場(chǎng)景需求出發(fā),一味定義成協(xié)變或逆變實(shí)際上限制了該泛型類對(duì)該類型形參使用的可能性,因?yàn)?out 只能是作為生產(chǎn)者,協(xié)變點(diǎn)位置有限制,而 in 只能是消費(fèi)者逆變點(diǎn)的位置也有限制。

那索性全都定義成不變型,那就在另一層面喪失了靈活性,就是它失去了子類型化關(guān)系,就是把它作為函數(shù)參數(shù)類型,外部只能傳入和它相同的類型,不可能存在子類型化關(guān)系的保留和反轉(zhuǎn)了

5. 協(xié)變點(diǎn)、逆變點(diǎn)的本質(zhì)

由上面的思考明白了一點(diǎn),使用協(xié)變、逆變的時(shí)候并不是那么死的按照協(xié)變點(diǎn),逆變點(diǎn)規(guī)則來(lái),可以更加靈活點(diǎn),關(guān)鍵是不能違背協(xié)變、逆變根本宗旨。協(xié)變宗旨就是定義的泛型類內(nèi)部不能存在寫操作的行為,對(duì)于逆變根本宗旨一般都是只寫的。

那 Kotlin 中 List<out E> 的源碼來(lái)說(shuō)都不是真正規(guī)則上說(shuō)的那樣協(xié)變,泛型形參 E 并不都是在協(xié)變點(diǎn) out 上,但是 List<out E> 內(nèi)部能夠保證不會(huì)存在寫操作危險(xiǎn)行為所以這種定義也是合法。實(shí)際上真正開(kāi)發(fā)過(guò)程,很難做到協(xié)變泛型類中的泛型類型形參都是在 out 協(xié)變點(diǎn)上,因?yàn)橛袝r(shí)候需求需要確實(shí)需要從外部傳入一個(gè)該類型形參的一個(gè)函數(shù)形參。

所以最終的結(jié)論是: 協(xié)變點(diǎn) out 和逆變點(diǎn) in 的位置的規(guī)則是一般大體情況下要遵守的,但是需要具體情況具體分析,針對(duì)設(shè)計(jì)的泛型類具體情況,適當(dāng)?shù)卦诓贿`背根本宗旨以及滿足需求情況下變下協(xié)變點(diǎn)和逆變點(diǎn)的位置規(guī)則

6. UnSafeVariance 注解在開(kāi)發(fā)中的應(yīng)用

由上面的本質(zhì)區(qū)別分析,嚴(yán)格按照協(xié)變點(diǎn)、逆變點(diǎn)規(guī)則來(lái)是不能完全滿足我們真實(shí)開(kāi)發(fā)需求場(chǎng)景的,所以有時(shí)候需要一道后門,那就要用特殊方式告訴它。那就是使用 UnSafeVariance 注解。所以 UnSafeVariance 注解作用很簡(jiǎn)單: 通過(guò) @UnSafeVariance 告訴編譯器該處安全性自己能夠把控,讓它放你編譯通過(guò)即可,如果不加編譯器認(rèn)為這是不合法的。

注解的意思就是不安全的型變,例如在協(xié)變泛型類中有個(gè)函數(shù)是以傳入一個(gè)該泛型形參的函數(shù)形參的,通過(guò) UnSafeVariance 注解讓編譯器閉嘴,然后把它放置在逆變點(diǎn)實(shí)際上是增加一層危險(xiǎn)性,相當(dāng)于把這層危險(xiǎn)交給了開(kāi)發(fā)者,只要開(kāi)發(fā)者能保證內(nèi)部不存在危險(xiǎn)性操作肯定就是安全的。

7. 協(xié)變、逆變、不變對(duì)比分析總結(jié)

7.1 分析對(duì)比

將從基本結(jié)構(gòu)形式、有無(wú)子類型化關(guān)系 (保留、反轉(zhuǎn))、有無(wú)型變點(diǎn) (協(xié)變點(diǎn) out、逆變點(diǎn) in)、角色 (生產(chǎn)者輸出、消費(fèi)者輸入)、類型形參存在的位置 (協(xié)變就是修飾只讀屬性和函數(shù)返回值類型;逆變就是修飾可變屬性和函數(shù)形參類型)、表現(xiàn)特征 (只讀、可寫、可讀可寫) 等方面進(jìn)行對(duì)比

協(xié)變 逆變 不變
基本結(jié)構(gòu) Producer<out E> Consumer<in T> MutableList<T>
子類型化關(guān)系 保留子類型化關(guān)系 反轉(zhuǎn)子類型化關(guān)系 無(wú)子類型化關(guān)系
有無(wú)型變點(diǎn) 協(xié)變點(diǎn) out 逆變點(diǎn) in 無(wú)型變點(diǎn)
類型形參存在的位置 修飾只讀屬性類型和函數(shù)返回值類型 修飾可變屬性類型和函數(shù)形參類型 都可以,沒(méi)有約束
角色 生產(chǎn)者輸出為泛型形參類型 消費(fèi)者輸入為泛型形參類型 既是生產(chǎn)者也是消費(fèi)者
表現(xiàn)特征 內(nèi)部操作只讀 內(nèi)部操作只寫 內(nèi)部操作可讀可寫

7.2 使用對(duì)比

實(shí)際上就是要明確什么時(shí)候該使用協(xié)變、什么時(shí)候該使用逆變、什么時(shí)候該使用不變。
實(shí)際上通過(guò)上述分析對(duì)比的表格可以得出結(jié)論:

首先,表格有很多個(gè)條件特征,到底是先哪個(gè)開(kāi)始判定條件好呢?實(shí)際上這里面還是需要選擇一下的。

假設(shè) 1: 就比如一開(kāi)始就以有無(wú)使用子類型化關(guān)系為條件做判定,這樣做法是有點(diǎn)問(wèn)題的,試想下在實(shí)際開(kāi)發(fā)中,先是去定義泛型類內(nèi)部一些方法和屬性的,這時(shí)候很難知道在外部使用情況下存不存在利用子類型化關(guān)系,也就是存不存在用子類型的值替換超類型的值場(chǎng)景,所以在剛剛定義泛型類的時(shí)候很難明確的。故還是先從泛型類定義的內(nèi)部特征著手會(huì)更加明確點(diǎn)。

假設(shè) 2:比如先根據(jù)泛型類內(nèi)部定義一些方法和屬性,由于剛開(kāi)始定義并不能確定是否是協(xié)變 out 還是逆變 in,所以上面的有無(wú)型變點(diǎn)不能作為判定條件,最開(kāi)始還沒(méi)確定的時(shí)候一般當(dāng)做不變泛型類來(lái)定義。最直白可以先看看型變點(diǎn),然后根據(jù)型變點(diǎn)基本確定泛型類內(nèi)部表現(xiàn)特征:

  • 步驟 1:首先,根據(jù)類型形參存在的位置初步判定;
  • 步驟 2:然后,通過(guò)判定表現(xiàn)特征是在泛型類定義內(nèi)部是不是只涉及到該泛型形參只讀操作 (協(xié)變或不變),還是寫操作 (逆變或不變),還是既可讀又可寫 (不變) 這里只能判斷出兩種組合情況 (協(xié)變或不變)、(逆變或不變) 中的一種,因?yàn)?strong>如果只涉及到讀操作那就是 (協(xié)變或不變),如果只涉及寫操作 (逆變或不變)。
  • 步驟 3:最后,再去看是否存在子類型化關(guān)系,如果通過(guò)步驟 2 得到是 (協(xié)變或不變) 外加有子類型化關(guān)系最終得到使用協(xié)變,如果通過(guò)步驟 2 得到是 (逆變或不變) 外加有子類型化關(guān)系最終得到使用逆變,如果沒(méi)有子類型化關(guān)系就用不變。

補(bǔ)充一點(diǎn),如果最終確定是協(xié)變的,可是在定義的時(shí)候通過(guò)步驟 1 得到類型形參存在的位置處于函數(shù)形參位置,那么這時(shí)候就可以大膽借助 @UnSafeVariance 注解告訴編譯器使得編譯通過(guò),逆變同理。
來(lái)張圖理解下:

圖片描述

7.3 理解對(duì)比

是否還記得上一篇文章開(kāi)頭的那個(gè)例子和那幅漫畫圖:

對(duì)于協(xié)變的理解,例子代碼如下:

fun main(args: Array<String>) {
    val stringList: List<String> = listOf("a", "b", "c", "d")
    val intList: List<Int> = listOf(1, 2, 3, 4)
    printList(stringList)//向函數(shù)傳遞一個(gè)List<String>函數(shù)實(shí)參,也就是這里L(fēng)ist<String>是可以替換List<Any>
    printList(intList)//向函數(shù)傳遞一個(gè)List<Int>函數(shù)實(shí)參,也就是這里L(fēng)ist<Int>是可以替換List<Any>
}

fun printList(list: List<Any>) {
//注意:List是協(xié)變的,這里函數(shù)形參類型是List<Any>,函數(shù)內(nèi)部是不知道外部傳入是List<Int>還是List<String>,全部當(dāng)做List<Any>處理
    list.forEach {
        println(it)
    }
}

理解:對(duì)于 printList 函數(shù)而言,它需要的是 List<Any> 類型是個(gè)相對(duì)具體類型更加泛化的類型,且在函數(shù)內(nèi)部的操作不會(huì)涉及到修改寫操作,然后在外部傳入一個(gè)更為具體的子類型肯定是滿足要求的泛化類型最基本需求。所以外部傳入更為具體子類型 List<String>、List<Int> 的兼容性更好。

對(duì)于逆變的理解,例子代碼如下:

class A<in T>{
    fun doAction(t: T){
        ...
    }
}

fun main(args: Array<String>) {

    val intA = A<Int>()
    val anyA = A<Any>()

    doSomething(intA)//不合法,
    doSomething(anyA)//合法
}

fun doSomething(a: A<Number>){//在doSomething外部不能傳入比A<Number>更為具體的類型,因?yàn)樵诤瘮?shù)內(nèi)部涉及寫操作.
    ....
}

理解:對(duì)于 doSomething,它需要的 A<Number> 是個(gè)相對(duì)泛化類型更加具體的類型,由于泛型類 A 逆變的,函數(shù)內(nèi)部的操作放開(kāi)寫操作權(quán)限,試著想下在 doSomething 函數(shù)外部不能傳入比他更為具體的比較器對(duì)象了,因?yàn)橹灰斜?A<Number> 更為具體的,就會(huì)出問(wèn)題,利用反證法來(lái)理解下,假如傳入 A<Int> 類型是合法的,那么在內(nèi)部函數(shù)還是當(dāng)做 A<Number>, 在函數(shù)內(nèi)部寫操作時(shí)候很有可能把它往里面寫入一個(gè) Float 類型的數(shù)據(jù),因?yàn)橥?Number 類型寫入 Float 類型是很合法的,但是外部實(shí)際上傳入的是 A<Int>,往 A<Int> 寫 Float 類型不出問(wèn)題才怪呢,所以原假設(shè)不成立。所以逆變放開(kāi)了寫權(quán)限,那么對(duì)于外部傳入的類型要求就更加嚴(yán)格了。

引出另一個(gè)問(wèn)題,為什么逆變寫操作是安全的呢? 細(xì)想也是很簡(jiǎn)單的,對(duì)于逆變泛型類型作為函數(shù)形參的類型,那么在函數(shù)外部的傳入實(shí)參類型就一定要比函數(shù)形參的類型更泛化不能更具體,所以在函數(shù)內(nèi)部操作的最具體的類型也就是函數(shù)形參類型,所以肯定可以大膽寫操作啊。就比如 A<Number> 類型形參類型,在 doSomething 函數(shù)中明確知道外部不能比它更為具體,所以在函數(shù)內(nèi)部大膽在 A<Number> 基礎(chǔ)上寫操作是可以的。

對(duì)于不變的理解,例子代碼如下:

fun main(args: Array<String>) {
    val stringList: MutableList<String> = mutableListOf("a", "b", "c", "d")
    val intList: MutableList<Int> = mutableListOf(1, 2, 3, 4)
    printList(stringList)//這里實(shí)際上是編譯不通過(guò)的
    printList(intList)//這里實(shí)際上是編譯不通過(guò)的
}

fun printList(list: MutableList<Any>) {
    list.add(3.0f)//開(kāi)始引入危險(xiǎn)操作dangerous! dangerous! dangerous!
    list.forEach {
        println(it)
    }
}

理解:不變實(shí)際上就更好理解了,因?yàn)椴淮嬖谧宇愋突P(guān)系,沒(méi)有所謂的子類型 A 的值在任何地方任何時(shí)候可以替換超類型 B 的值的規(guī)則,所以上述例子編譯不過(guò),對(duì)于 printList 函數(shù)而言必須接收的類型是 MutableList<Any>,因?yàn)橐坏﹤魅牒退灰粯拥木唧w類型就會(huì)存在危險(xiǎn)操作,出現(xiàn)不安全的問(wèn)題。

8. 總結(jié)

到這里有關(guān) Kotlin 中泛型型變的核心概念就闡述完畢了,文章有點(diǎn)長(zhǎng)可以需要好好消化,重在理解以及一些實(shí)際的場(chǎng)景上應(yīng)用。其實(shí)對(duì)于 Kotlin 協(xié)變和不變倒是很好理解,可能大家對(duì)于逆變還是需要好好理解。下篇文章將是泛型型變的一些應(yīng)用和一些其他概念研究比如星投影之類的。