kotlin中的高級特性--協變與逆變(反變)

逆變性與協變性是kotlin中相對於java的新特性,這個成爲不少java轉kotlin學習的一個坎,在這篇文章裏我將詳細介紹和推導逆變性與協變性的由來。

內容參考了以下兩篇博客:
http://www.cnblogs.com/lemontea/archive/2013/02/17/2915065.html
http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html

在此之前我們需要明白一個大前提:

java不允許向下轉型(父類轉換成子類)

定義

假設有這樣兩個類型:TSub是TParent的子類,顯然TSub型引用是可以安全轉換爲TParent型引用的。如果一個泛型接口IFoo<T>,IFoo<TSub>可以轉換爲IFoo<TParent>的話,我們稱這個過程爲協變,而且說這個泛型接口支持對T的協變。而如果一個泛型接口IBar<T>,IBar<TParent>可以轉換爲T<TSub>的話,我們稱這個過程爲反變(contravariant),而且說這個接口支持對T的反變。因此很好理解,如果一個可變性和子類到父類轉換的方向一樣,就稱作協變;而如果和子類到父類的轉換方向相反,就叫反變性。

我們來具體看一下體現到kotlin語法中是什麼樣的

kotlin中有out和in關鍵字來表示協變和逆變,我們通過out的兩個來認識什麼是逆變:
1. 泛型只能在返回值中出現
2. 只能進行子類向父類的轉型

eg:
//有如下兩個類
//1.不支持逆變與協變
MyFuncA<T>
//2.支持協變
MyFuncB<out T>
//現對其進行初始化然後轉型
MyFuncA<object> funcAObject = null;
MyFuncA<string> funcAString = null;
MyFuncB<object> funcBObject = null;
MyFuncB<string> funcBString = null; 
funcAObject = funcAString;//編譯失敗,MyFuncA不支持逆變與協變
funcBObject = funcBString;//變了,協變
funcBObject = funcBInt;//編譯失敗,值類型不參與協變或逆變

代碼中可以看出使用了協變的泛型對象MyFuncB<out T>可以進行子類向父類的轉換,而不支持逆變和協變得MyFuncA<T>則不允許向上或者是向下的轉換。

其實以上的兩條含義只是一條,只不過在不同的場景下表現不一樣而已,我們一起來看一下:

假設有這樣一個方法:

String Base<out T>
{
  void Test(T t)
}

泛型協變的,但我們允許有方法可以在參數中使用泛型(實際上這樣是不行的,這裏我們通過反正法證明來證明這一結論

Base<object> BaseObject = null;
Base<string> BaseString = null;
BaseObject = BaseString;
BaseObject.Test("");

我們來看一下函數的調用過程:

clipboard.png

BaseObjectBaseString初始化,所以
BaseObject.Test("")的調用實質上是調用BaseString.Test(""),而BaseString中要的泛型Tstring,而實際BaseObject給出的泛型Tobject
object無法向下轉型爲string,因此出現類型轉換的異常。

由此我們得出以上結論,因爲泛型是協變的,進行子類向父類的轉型,所以泛型不能在傳入參數中使用,只能在返回值中使用。

逆變性反之也是一樣的推導,由於進行的是父類向子類的轉型,在返回值返回的時候要求的是子類的泛型,但實際上是調用父類的方法返回了父類,同樣出現了向下轉型的錯誤,因此逆變性中泛型只能在傳入參數中使用,不能在返回值中使用。

eg:

//過程同上
T Base<in T>.Test()

泛型逆變的,但我們允許有方法可以在返回值中使用泛型(實際上這樣是不行的,這裏我們同樣通過反正法證明來證明這一結論)

Base<object> BaseObject = null;
Base<string> BaseString = null;
BaseString = BaseObject ;
BaseString.Test();

只要按照協變時的調用方法看代碼的調用就會發現我們在返回值的時候得到的是object,而我們要的是string,同樣出現向下轉型的錯誤。

逆變和協變是保證運行時安全而出現的機制,編碼時編譯器已經強制我們在逆變中不能在函數返回值中使用泛型,在協變中不能在函數參數中使用泛型,以保證運行時的安全,也就是將我們可能產生的類型轉換異常在編譯階段給解決了!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章