標簽: Kotlin
本文聲明:
本文由Coder-pig編寫,想了解其他內容,可見【Coder-Pig的豬欄】
尊重作者勞動成果,未經本人授權,禁止轉載!違者必究!
《Kotlin搞起來》系列目錄地址:http://blog.csdn.net/coder_pig/article/details/72851862
PS:本來是想把泛型加到上一節的類與對象中的,后來發現Kotlin文檔中對泛型的
介紹令人難以理解,而且很多小伙伴對Java中的泛型也是一知半解,所以還是把
泛型這一章抽取出來,順便捋一捋一些概念性的東西。
Java泛型的引入
問題引入:
集合框架能存儲各種對象,但是取出對象的時候可能出現類型不兼容的異常,比如:
往集合中插入String和Integer的對象,取出的對象可能是String或Integer類型的,
如果不用instanceof()判斷對象類型,再用對應類型的變量存儲的話,就可能會發
生ClassCastException(類型轉換異常)
疑問:
每次取元素都要進行判斷,顯得非常繁瑣,能不能在創建的時候就指定元素類型?
讓編譯器負責檢查添加元素的合法性?
回答:
當然可以,jdk 1.5引入的泛型(Generice)能幫我們解決問題,將確定不變的類型參數化,
從而保證集合中存儲的元素類型都是同一種,而且所有的強轉都是自動與隱式的,
從而提高了代碼的重用率。
Java中泛型的使用
泛型類 :

泛型接口:

泛型方法:

注意事項:
- 1.泛型的類型參數只能是類類型,不能是簡單數據類型(int那些);
- 2.泛型可以有多個泛型參數;
- 3.有時我們想讓集合存取的元素為某個類子類或者父類而非特定一個類,可以使用
extends和super進行約束,這個又稱為有界類型,比如有個父類是People,然后他有
幾個子類,Student,Worker,Police,想允許People和他的三個子類都運行放到集合中,
直接寫成<T extends People>就可以了,這個稱上界約束,而super則稱下界約束。
Java假泛型實現原理
和C#中的泛型不同,Java和Kotlin中的泛型都是假泛型,實現原理就是:類型擦除(Type Erasure),
Java編譯器在生成Java字節碼種是不包含泛型中的類型信息的,使用泛型的時候加上的類型參數,
會在編譯器編譯的時候去掉,然后用其限定類型替換!
比如下述代碼,獲取到的類類型都是一樣的(ArrayList)


因為編譯時會用限定類型替換,所以如果這里我們用反射往一個Integer類型的List插入
String類型的參數是不會報錯的:


PS:關于類型擦除更多內容可見:http://blog.csdn.net/lonelyroamer/article/details/7868820
Java泛型通配符的引入
先來看一段代碼:

我們都知道在Java中Object類是所有類的默認父類,按理來說應該是可以轉型的。
然而編譯器卻報錯了:

這里我們假設沒報錯,這個時候如果我們往list2里添加一個整數:

假設也沒報錯,但是運行的時候肯定是會報ClassCastException錯誤的!
Java中的泛型是不型變的,意味著 List<String>并不是List<Object>的子類型,
Java以此方式來保證運行時的安全。為了解決這個問題,Java中又引入了泛型通配符:?
<? extends E>
表示該方法只接受E或者E的子類,但不知道是哪個具體的子類型,所以只能讀不能寫
從而使得類型是協變的,又叫上界限定通配符。看到Collection接口里的addAll方法就了解了:

<? super E>
對應的逆變則是只能接受E或者E的父類,因為無法判斷讀取到的類型,所以只能寫不能讀,
又叫下界限定通配符。
<?>
最后還有個無限定通配符,類型完全位置,此時只能讀不能寫,這個叫不變。
Kotlin中的型變
基本的泛型用法比較簡單,和Java中的基本類似,難點還是型變,Kotlin中沒有通配符類型,
但有其他兩個東西:聲明處型變(declaration-site variance)與 類型投影(type projections)。
聲明處型變
在Java里,如果你寫這樣一段代碼,編譯器是不會通過的:

按照我們的理解來說,上面的代碼應該是沒問題的,但是編譯器不知道,仍然禁止這樣的操作,
我們可以聲明通配符對象類型為:

使用了更復雜的類型并沒有任何意義,依舊可以調用對象的所有相同的方法,編譯器依舊不知道。
Kotlin中,有一種方法向編譯器解釋這種情況,稱為聲明處型變。使用到的修飾符是:in 和 out
out:協變,只能用作輸出,可以作為返回值類型,但無法作為入參類型

輸出結果:

in:逆變,只能用作輸入,可以作為如殘類型,但無法作為返回值類型

類型投影
使用處型變,有些類實際上不能限制為只返回T,該類在T上既不能是協變的也不能是逆變的,
然后根據不同的方法,你可以使用in 或 out進行限制,比如:
fun copy(from: Array<out Any>, to: Array<Any>) { /* …… */ }
fun fill(dest: Array<in String>, value: String) { /* …… */ }
*投影
和Java中的?通配符類似,Kotlin可以根據 * 所指代的泛型參數進行相應的映射,官方解釋如下:
- 對于 Foo <out T>,其中 T 是一個具有上界 TUpper 的協變類型參數,Foo < * > 等價于 Foo <out TUpper>。這意味著當 T 未知時,你可以安全地從 Foo <*> 讀取 TUpper 的值。
- 對于 Foo <in T>,其中 T 是一個逆變類型參數,Foo < * > 等價于 Foo <in Nothing>。
這意味著當 T 未知時,沒有什么可以以安全的方式寫入 Foo < * >。- 對于 Foo <T>,其中 T 是一個具有上界 TUpper 的不型變類型參數,Foo< * > 對于讀取值時等價于
Foo<out TUpper> 而對于寫值時等價于 Foo<in Nothing>。
注意事項:
- 不允許作為函數和變量的類型的泛型參數!
- 不允許作為函數和變量的類型的泛型參數!
- 不能直接作為父類的泛型參數傳入!
PS:關于Kotlin中泛型最后的幾點我還是有點模糊的,實際開發沒用過,體會不夠深刻,等以后
用到了有所收獲后再來對這部分重新編輯~
本文參考文獻: