Kotlin中的函數(shù)式編程(3):泛型

和Java一樣,Kotlin也提供了對泛型的支持,即允許定義類型形參。在執(zhí)行的過程中,類型形參將會轉(zhuǎn)變?yōu)轭愋蛯崊⒌木唧w類型。

在Kotlin中聲明泛型函數(shù)

如果想編寫一個對于任何類型都有效的函數(shù),可以聲明泛型函數(shù)。其步驟為先申明類型形參,然后在接收者或者返回類型中使用類型形參。


聲明泛型函數(shù)

可以看到,當(dāng)類型形參用于接收者的時候,接收者的類名后面有泛型標(biāo)志(途中的<E>),這種類后面加上尖括號的類稱為泛型類

在Kotlin中聲明泛型類/接口


可以看到,F(xiàn)ixed接口定義了類型參數(shù)T,在這一點上,Kotlin似乎和Java保持一樣。

和Java一樣,也可以為泛型指定上界,進行定義約束,例如:


類型參數(shù)的上界約束


在這里,T : Animal 等于Java中的 T extends Animal,值得一提的是,當(dāng)我們指定上界了以后,就可以調(diào)用上界類的方法了(例如sleep)

需要注意的是,Kotlin也是基于Jvm的語言,因此,在運行時也會發(fā)生類型擦除,這意味者泛型類實例不會攜帶用于創(chuàng)建它的類型實參的信息。比如,在運行時,程序并不知道List<String>和List<Int>的區(qū)別,只知道它們都是List:


類型擦除的后果

可以看到,系統(tǒng)在編譯期是知道其攜帶的類型實參的,它阻止了往list1中添加String集合的操作。但是,如果我們采用欺騙手段繞過編譯器的檢查,則程序會嘗試將list2的元素加入list1中,在運行時發(fā)生錯誤。Java的類型擦除其原意是為了使應(yīng)用程序的內(nèi)存容量減少(因為需要保存在內(nèi)存的類型信息更少)。因此,對于泛型類的處理,應(yīng)該盡量在編譯期發(fā)現(xiàn)問題并進行處理。

但是編譯期無法處理的問題怎么辦?例如,我們想在一個集合中篩選出我們所需要的類的實例:


為什么無法通過編譯呢,因為在運行時,R這個泛型的信息已經(jīng)被擦除了,程序并不知道Class這個泛型類的類型實參,自然也無法檢查集合中的元素是否為R類型。

這就是很矛盾的地方了,我們在調(diào)用這個函數(shù)的時候,肯定已經(jīng)顯式地傳入了泛型參數(shù)R的類型,但是由于類型擦除,使得這些信息丟失了。因此,我們必須在每一次調(diào)用發(fā)生的地方獲取這些信息。而內(nèi)聯(lián)函數(shù),恰好會在生成的字節(jié)碼中引用具體類。

使用內(nèi)聯(lián)函數(shù)申明帶實化類型的函數(shù)

如果用inline標(biāo)記一個函數(shù),則編譯器會把每一次函數(shù)調(diào)用都換成函數(shù)實際的代碼實現(xiàn),因此,內(nèi)聯(lián)函數(shù)可以在調(diào)用發(fā)生的時候獲取類型實參,在函數(shù)定義時用reified?標(biāo)記需要獲取類型參數(shù)


在內(nèi)聯(lián)函數(shù)中獲取類型實參

基于這個特性,內(nèi)聯(lián)函數(shù)常常用于一些接收Class類型參數(shù)的函數(shù),比如,以下函數(shù)對startActivity進行了簡化:


泛型的變形

在Java中有一個經(jīng)典的問題,即List<Integer>和List<Number>之間有什么關(guān)聯(lián)。在Java中我們已經(jīng)很明確了,List<Integer> 不是 List<Number>,反過來List<Number>也不是List<Integer>,如果你的函數(shù)以List<Number>作為參數(shù),則不能把一個List<Integer>對象作為參數(shù)傳入。

從表面上看,一個函數(shù)如果能處理List<Number>,那么它也應(yīng)該能處理List<Integer>,例如,一個函數(shù)對List<Number>內(nèi)的所有元素進行求和運算,那么它也應(yīng)該對List<Integer>有效,因為它會讀取并使用所有的元素。咋眼一看,似乎用List<Integer>代替List<Number>并無不妥之處,反正都可以讀取、求和嘛。

但是真的是這樣么?如果一個函數(shù)需要把其他元素(Number)添加進來,如果添加的Number不是Integer類型的,顯然,List<Integer>在這種場合就不能替代List<Number>)了,但是List<Number>取代List<Integer>倒是很安全的,因為它也能存儲int。

因此可以看出,泛型類之間的替代關(guān)系是存在一定條件的,為了討論類型之間的關(guān)系,需要引入一個新的概念-子類型。

如果需要使用類型A的值,你都能夠使用類型B的值(當(dāng)做A使用),則B為A的子類型。

注意,子類型并不等于子類

泛型的協(xié)變與逆變

如果B是A的子類,B類型又是A類型的子類型的話,這意味著子類型化關(guān)系得到了保留,稱為協(xié)變

要聲明某個類/接口在某個類型參數(shù)上是可以協(xié)變的,在類型前面參數(shù)加上out即可


顯然,把一個子類然后轉(zhuǎn)換成父類是絕對安全的,可以看到,接口接收一個類型形參(它是T的子類),函數(shù)返回了T類型,我們稱作生產(chǎn)了T,因此用out標(biāo)識

如果B是A的子類,A類型又是B類型的子類型的話,這意味著子類型化關(guān)系逆轉(zhuǎn),稱為逆變


例如,一個能比較基類的比較器顯然可以適用于這個基類的子類之間的比較,在這種情況下,E被當(dāng)作函數(shù)的參數(shù),稱作消費了T,用in標(biāo)識

如果你的接口/類中 既把類型形參作為函數(shù)的參數(shù)使用,又作為了參數(shù)返回,那就不能使用in或者out了(即消費并生產(chǎn)對象,例如可變列表MutableList等)

最后編輯于
?著作權(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)容

  • Kotlin 知識梳理系列文章 Kotlin 知識梳理(1) - Kotlin 基礎(chǔ)Kotlin 知識梳理(2) ...
    澤毛閱讀 2,751評論 0 4
  • 前言 人生苦多,快來 Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,702評論 9 118
  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 8,129評論 12 51
  • 第8章 泛型 通常情況的類和函數(shù),我們只需要使用具體的類型即可:要么是基本類型,要么是自定義的類。但是在集合類的場...
    光劍書架上的書閱讀 2,199評論 6 10
  • 藝術(shù)作品是作者與觀者共同建構(gòu)的結(jié)果,講我看到的《西游記之三打白骨精》。 在古往今來的故事里,跟“惡 愚 貪”的博弈...
    ATCATM閱讀 434評論 0 0

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