Kotlin 初識(shí)泛型型變
相信有很多初學(xué)者對(duì) Kotlin 中的泛型型變都是一知半解,泛型型變概念太多了,而且每個(gè)概念和后面都是相關(guān)的,只要前面有一個(gè)地方未理解后面的難點(diǎn)更是越來越看不懂。Kotlin 的泛型比 Java 中的泛型多了一些新的概念,比如子類型化關(guān)系、逆變、協(xié)變、星投影的。個(gè)人認(rèn)為學(xué)好 Kotlin 的泛型主要有這么幾個(gè)步驟:
- 深入理解泛型中每個(gè)小概念和結(jié)論,最好能用自己的話表述出來;
- 通過分析 Kotlin 中的相關(guān)源碼驗(yàn)證你的理解和結(jié)論;
- 通過實(shí)際的例子鞏固你的理解。
1. 泛型為什么會(huì)存在型變?
首先,我們需要明確兩個(gè)名詞概念: 基礎(chǔ)類型和實(shí)參類型。例如對(duì)于 List<String>
, List
就是基礎(chǔ)類型而這里的 String
就是實(shí)參類型。
然后,我們需要明確一下,這里的型變到底指的是什么?可以先大概描述一下,它反映的是一種特殊類型的對(duì)應(yīng)關(guān)系規(guī)則。是不是很抽象?
那就先來看個(gè)例子,例如 List<String>和List<Any>
他們擁有相同的基礎(chǔ)類型,實(shí)參類型 String
和 Any
存在父子關(guān)系,那么是不是 List<String>
和 List<Any>
是否存在某種對(duì)應(yīng)關(guān)系呢?實(shí)際上,我們討論的型變也就是圍繞著這種場(chǎng)景展開的。有了上面的認(rèn)識(shí),進(jìn)入正題為什么需要這種型變關(guān)系呢?來看對(duì)比的例子,我們需要向一個(gè)函數(shù)中傳遞參數(shù)。
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>) {
//注意:這里函數(shù)形參類型是List<Any>,函數(shù)內(nèi)部是不知道外部傳入是List<Int>還是List<String>,全部當(dāng)做List<Any>處理
list.forEach {
println(it)
}
}
上述操作是合法的,運(yùn)行結(jié)果如下:
如果我們上述的函數(shù)形參 List<Any>
換成 MutableList<Any>
會(huì)變成什么樣呢?
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í)際上是編譯不通過的
printList(intList)//這里實(shí)際上是編譯不通過的
}
fun printList(list: MutableList<Any>) {
list.add(3.0f)//開始引入危險(xiǎn)操作dangerous! dangerous! dangerous!
list.forEach {
println(it)
}
}
我們來試想下,利用反證法驗(yàn)證下,假如上述代碼編譯通過了,會(huì)發(fā)生什么,就會(huì)發(fā)生下面的可能出現(xiàn)類似的危險(xiǎn)操作。就會(huì)出現(xiàn)一個(gè) Int 或者 String 的集合中引入其他的非法數(shù)據(jù)類型,所以肯定是有問題的,故編譯不通過。
因?yàn)槲覀冋f過在函數(shù)的形參類型 MutableList<Any>
在函數(shù)內(nèi)部它只知道是該類型也不知道外部給它傳了個(gè)啥,所以它只能在內(nèi)部按照這個(gè)類型規(guī)則來,所以在函數(shù)內(nèi)部 list.add(3.0f)
這行代碼時(shí)編譯通過的,向一個(gè) MutableList<Any>
集合加入一個(gè) Float 類型明顯說得過去的。
通過對(duì)比上面兩個(gè)例子,大家有沒有思考一個(gè)問題就是為什么 List<String>、List<Int>替換List<Any>
可以,而 MutableList<String>、MutableList<Int>替換MutableList<Any>
不可以呢?
實(shí)際上問題所說的類型替換其實(shí)就是型變 , 那大家到這就明白了為什么會(huì)存在型變了,型變更為了泛型接口更加安全,假如沒有型變,就會(huì)出現(xiàn)上述危險(xiǎn)問題。
那另一問題來了為什么有的型變關(guān)系可以,有的不可以呢?對(duì)于傳入集合內(nèi)部不會(huì)存在修改添加其元素的操作 (只讀),是可以支持外部傳入更加具體類型實(shí)參是安全的,而對(duì)于集合內(nèi)部存在修改元素的操作 (寫操作) 是不安全的,所以編譯器不允許。
以上面例子分析,List<Any>
實(shí)際上一個(gè)只讀集合 (注意:它和 Java 中的 List 完全不是一個(gè)東西,注意區(qū)分),它內(nèi)部不存在 add,remove
操作方法,不信的可以看下它的源碼,所以以它為形參的函數(shù)就可以敞開大門大膽接收外部參數(shù),因?yàn)椴淮嬖谛薷脑夭僮魉允前踩?,所以第一個(gè)例子是編譯 OK 的;
而對(duì)于 MutableList<Any>
在 Kotlin 中它是一個(gè)可讀可寫的集合,相當(dāng)于 Java 中的 List, 所以它的內(nèi)部存在著修改、刪除、添加元素的危險(xiǎn)操作方法,所以對(duì)于外部傳入的函數(shù)形參它需要做嚴(yán)格檢查必須是 MutableList<Any>
類型。
為了幫助理解和記憶,自己繪制了一張獨(dú)具風(fēng)趣的漫畫圖幫助理解,這張圖很重要以致于后面的協(xié)變、逆變、不變都可以從它獲得理解。后面也會(huì)不斷把它拿出來分析
最后為了徹底把這個(gè)問題分析透徹可以給大家看下 List<E>
和 MutableList<E>
的部分源碼
public interface List<out E> : Collection<E> {
// Query Operations
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
// Bulk Operations
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
...
}
public interface MutableList<E> : List<E>, MutableCollection<E> {
// Modification Operations
override fun add(element: E): Boolean
override fun remove(element: E): Boolean
// Bulk Modification Operations
override fun addAll(elements: Collection<E>): Boolean
...
}
仔細(xì)對(duì)比下 List<out E>
和 MutableList<E>
泛型定義是不一樣的,他們分別對(duì)應(yīng)了協(xié)變和不變 , 至于什么是協(xié)變什么是逆變什么不變,我們后面會(huì)詳細(xì)講。
2. 子類、子類型、超類型概念梳理
我們一般說子類就是派生類,該類一般會(huì)繼承它的超類。例如: class Student: Person()
, 這里的 Student
一般稱為 Person
的子類,Person
是 Student
的超類。
而子類型和超類型定義則完全不一樣,我們從上面類和類型區(qū)別就知道一個(gè)類可以有很多類型,那么子類型不僅僅是想子類那樣繼承關(guān)系那么嚴(yán)格。
子類型定義的規(guī)則一般是這樣的: 任何時(shí)候如果需要的是 A 類型值的任何地方,都可以使用 B 類型的值來替換的,那么就可以說 B 類型是 A 類型的子類型或者稱 A 類型是 B 類型的超類型??梢悦黠@看出子類型的規(guī)則會(huì)比子類規(guī)則更為寬松。那么我們可以一起分析下面幾個(gè)例子:
Tips:某個(gè)類型也是它自己本身的子類型,很明顯 Person 類型的值任意出現(xiàn)地方,Person 肯定都是可以替換的。屬于子類關(guān)系的一般也是子類型關(guān)系。像 String 類型值肯定不能替代 Int 類型值出現(xiàn)的地方,所以它們不存在子類型關(guān)系
再來看個(gè)例子,所有類的非空類型都是該類對(duì)應(yīng)的可空類型的子類型,但是反過來說就不行,就比如 Person
非空類型是 Person?
可空類型的子類型,很明顯嘛,任何 Person?
可空類型出現(xiàn)值的地方,都可以使用 Person
非空類型的值來替換。
其實(shí)這些我在開發(fā)過程中是可以體會(huì)得到的,比如細(xì)心的同學(xué)就會(huì)發(fā)現(xiàn),我們?cè)?Kotlin 開發(fā)過程,** 如果一個(gè)函數(shù)接收的是一個(gè)可空類型的參數(shù),調(diào)用的地方傳入一個(gè)非空類型的實(shí)參進(jìn)去是合法的。** 但是如果一個(gè)函數(shù)接收的是非空類型參數(shù),傳入一個(gè)可空類型的實(shí)參編譯器就會(huì)提示你,可能存在空指針問題,需要做非空判斷。因?yàn)槲覀冎婪强疹愋捅瓤煽疹愋透踩矸鶊D理解下:
3. 什么是子類型化關(guān)系
相信到了這,大家應(yīng)該自己都能猜出什么是子類型化關(guān)系吧?它是實(shí)際上就是我們上面所講的那些。
3.1 子類型化關(guān)系
大致概括一下: 如果 A 類型的值在任何時(shí)候任何地方出現(xiàn)都能被 B 類型的值替換,B 類型就是 A 類型的子類型,那么 B 類型到 A 類型之間這種映射替換關(guān)系就是子類型化關(guān)系
3.2 回答最開始的問題
現(xiàn)在我們也能用 Kotlin 中較為專業(yè)的術(shù)語子類型化關(guān)系來解釋最開始那個(gè)問題為什么以 List<String>,List<Int>
類型的函數(shù)實(shí)參可以傳遞給 List<Any>
類型的函數(shù)形參,而 MutableList<String>,MutableList<Int>
類型的函數(shù)實(shí)參不可以傳遞給 MutableList<Any>
類型的函數(shù)形參?
因?yàn)?List<String>,List<Int>
類型是 List<Any>
類型的子類型,所以 List<Any>
類型值出現(xiàn)的地方都可以使用 List<String>,List<Int>
類型的值來替換。而 MutableList<String>,MutableList<Int>
類型不是 MutableList<Any>
的子類型也不是它的超類型,所以當(dāng)然就不能替換了。
3.2 由上面回答引出一個(gè)細(xì)節(jié)點(diǎn)
仔細(xì)分析觀察下上面所說的,List<String>,List<Int>
類型是 List<Any>
類型的子類型,然后再細(xì)看針對(duì)都具有相同的 List
這個(gè)基礎(chǔ)類型的泛型參數(shù)類型對(duì)應(yīng)關(guān)系,這里的 String,Int
類型是 Any
類型的子類型 (注意:我們?cè)诜盒椭卸紤?yīng)該站在類型和子類型的角度來看問題,不要在局限于類和子類繼承層面啊,這點(diǎn)很重要,因?yàn)?List<String>
還是 List<String?>
子類型呢,所以和繼承層面子類沒有關(guān)系),然后 List<String>,List<Int>
類型也是 List<Any>
類型的子類型,這種關(guān)系叫做保留子類型化關(guān)系,也就是所謂的協(xié)變。具體下篇文章著重分析。
4. 總結(jié)
本篇文章可以說是下篇文章的一個(gè)概念理解的基礎(chǔ),下篇很多高級(jí)的概念和原理都是在這篇文章延伸的,建議好好消化這些概念,這里最后再著重強(qiáng)調(diào)一下:一定需要好好理解什么是子類型,它和子類有什么區(qū)別。實(shí)際上 Kotlin 中的泛型型變的基礎(chǔ)就是子類型化關(guān)系,一般在這我們都是站在類型和子類型角度分析關(guān)系,而不是簡(jiǎn)單的類和子類繼承層面。還有就是有沒有思考過為什么要弄這么一套復(fù)雜的型變關(guān)系,其實(shí)仔細(xì)想想就為了泛型類操作和使用更加安全,避免引入一些存在危險(xiǎn)隱患。下篇文章就是泛型中的高級(jí)概念,其中包括泛型協(xié)變、逆變等,只要把這篇文章概念理解清楚了后面會(huì)很簡(jiǎn)單的。