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.htmlhtml

在此以前咱們須要明白一個大前提:

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

定義

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

咱們來具體看一下體現到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>則不容許向上或者是向下的轉換。ide

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

假設有這樣一個方法:學習

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

泛型協變的,但咱們容許有方法能夠在參數中使用泛型(實際上這樣是不行的,這裏咱們經過反正法證實來證實這一結論編碼

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

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

clipboard.png

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

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

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

eg:

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

泛型逆變的,但咱們容許有方法能夠在返回值中使用泛型(實際上這樣是不行的,這裏咱們一樣經過反正法證實來證實這一結論)

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

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

逆變和協變是保證運行時安全而出現的機制,編碼時編譯器已經強制咱們在逆變中不能在函數返回值中使用泛型,在協變中不能在函數參數中使用泛型,以保證運行時的安全,也就是將咱們可能產生的類型轉換異常在編譯階段給解決了!
相關文章
相關標籤/搜索