衆所周知,Java 5才最大的亮點就是引入泛型,那麼Java引入泛型的目的是什麼?這就須要查看Java 5引入泛型前的代碼:(由於Java向後兼容,如今這段代碼還能編譯成功)html
#daqiJava.java
List list = new ArrayList();
list.add("");
String str = (String) list.get(0);
//添加錯誤類型
list.add(1);
複製代碼
因爲ArrayList
底層是依靠Object數組
實現的,這使得任何類型均可以添加到同一個ArrayList
對象中。且取出來時是Object類型,須要強制類型轉換後才能進行相應的操做。但因爲ArrayList
對象能接受任何類型,沒法保證類型轉換老是正確的,很容易形成ClassCastException異常。java
但泛型的出現,讓這一切都迎刃而解。單個ArrayList
對象只能存儲特定類型的對象,若是不是存入該類型或者該類型子類的對象,編譯器會報錯提醒,規範了ArrayList
中對象的類型。同時,取出來時能夠安心的依據泛型的具體類型進行強制類型轉換,而且這是在ArrayList
中自動完成強轉的,省去了開發者進行強制類型轉換帶來的繁瑣。android
#daqiJava.java
List<String> list = new ArrayList();
list.add("");
String str = list.get(0);
list.add(1);//編譯器不經過
複製代碼
總的來講,泛型帶來如下好處:數組
類型參數約束能夠限制做爲泛型類和泛型函數的類型實參的類型。安全
把一個類型指定爲泛型的類型形參的上界約束,在泛型類型具體的初始化中,對應的類型實參必須是這個具體類型或它的子類型。bash
換句話說就是,某泛型函數(例如求和函數)能夠用在List<Int>
和List<Double>
上,但不能夠用在List<String>
上。這時能夠指定泛型類型型參的上界爲Number
,使類型參數必須使數字。ide
fun <T:Number> sum(num1:T,num2:T):T{
}
複製代碼
一旦指定上界,只有 Number
的子類(子類型)能夠替代 T。函數
尖括號中只能指定一個上界,若是同一類型參數須要多個上界,須要使用 where-子句:post
fun <T> daqi(list: List<T>)
where T : CharSequence,T : Comparable<T> {
}
複製代碼
類型參數約束默認的上界是 Any?
。意味着泛型函數接收的參數可空,儘管泛型T並無標記? 。這時可使用<T : Any>
替換默認上界,確保泛型T永遠爲非空類型。學習
學習泛型的型變以前,須要先學習本小節的內容,以便更好的理解後面的泛型的型變。在Java中,咱們每每會把類和類型看成相同的概念來使用,但其實它們是兩種不一樣概念。區分類和類型這兩種概念的同時,也須要分狀況討論:
非泛型類的名稱能夠直接看成類型使用。而在Kotlin中,一個非泛型類至少能夠分爲兩種類型:非空類型和可空類型。例如String
類,能夠分爲可空類型String?
和 非空類型String
.
而對於泛型類就變得更爲複雜了。一個泛型類想獲得合法的類型,必須用一個具體的類型做爲泛型的類型形參。所以一個泛型類能夠衍生出無限數量的類型。例如:Kotlin的List
是一個類,不是一個類型。其合法類型:List<String>
、List<Int>
等。
咱們通常將一個類的派生類稱爲子類,該類稱爲父類(基類)。例如:Int
是Number
的派生類,Number
做爲父類,Int
做爲子類。
而子類型與子類的定義不同,子類型的定義:
任什麼時候候指望A類型的值時,可使用B類型的值,則B就是A的子類型
超類型是子類型的反義詞。若是B是A的子類型,那麼反過來A就是B的超類型。
對於非泛型類,其類型會沿襲該類的繼承關係,當A是B的父類,同時A類型也是B類型的超類型。當指望A類型的對象時,可使用B類型的對象進行傳遞。
全部類的 非空類型 都是該類的 可空類型 的子類型,但反過來不能夠。例如:在接收String?
類型的地方,可使用String
類型的值來替換。但不能將String?
類型的值存儲到String
類型的值中,由於null
不是非空類型變量能夠接收的值。(除非進行判空或非空斷言,編譯器將可空類型轉換爲非空類型,這時原可空類型的值能夠存儲到非空類型的變量中)
做爲非泛型類,Int
和String
沒有繼承關係,二者間不存在子類型或超類型的關係。
咱們都知道非泛型類其類型會沿襲該類的繼承關係。但對於泛型類,這是行不通的。例如如下代碼,是沒法編譯成功的:
#daqiJava.java
List<String> strList = new ArrayList();
List<Object> objList = new ArrayList();
objList = strList;
複製代碼
List<Object>
和List<String>
是兩個相互獨立的類型,不存在子類型的關係。即使String
類的基類是Object
類。
由於當你指望List<Object>
時,容許賦值一個List<String>
過來,也就意味着其餘的類型(如List<Int>
等)也能賦值進來。這就形成了類型不一致的可能性,沒法確保類型安全,違背了泛型引入的初衷 —— 確保類型安全。
到這裏你或許會想,對於接收泛型類對象的方法,這不就"削了"泛型類的代碼通用性(靈活性)的能力?Java提供了有限制的通配符來確保類型安全,容許泛型類構建相應的子類型化關係,提升代碼的通用性(靈活性)。與之對應的,即是Kotlin的型變。Kotlin中存在協變和逆變兩種概念,統稱爲聲明處型變。
Kotlin的聲明處型變包含了協變和逆變。協變和逆變都是用於規範泛型的類型形參的範圍,確保類型安全。
保留子類型化關係
具體意思是:當 B 是 A 的子類型,那麼List<B>
就是List<A>
的子類型。協變類保留了泛型的類型形參的子類型化關係。
public fun Out(list: List<out String>) {
}
複製代碼
反轉子類型化關係
具體意思是:當 B 是 A 的子類型,那麼List<A>
就是List<B>
的子類型。逆變類反轉了泛型的類型形參的子類型化關係。
public fun In(list: MutableList<in String>) {
}
複製代碼
對於協變的定義廣泛很容易理解,但對於逆變每每比較費解。因此我決定退一步,藉助Java的有限制的通配符進行了解。從官方文檔中瞭解到,協變、逆變和Java的通配符類型參數有如下關係:
通配符類型參數 ? extends A 表示接受 A 或者 A 的子類型。
通配符類型參數 ? super A 表示接受 A 或者 A 的超類型。
因此,out Number
和 in Number
的"取值範圍"能夠用一張圖歸納(暫時只考慮由非泛型類的繼承帶來的子類型化關係):
Number
類具備Int
、Long
等派生類,同時也擁有Any
這個基類。當須要依據Number
進行協變時(即<out Number>
),泛型的類型形參只能選取Number
自身以及其子類(子類型)。當須要依據Number
進行逆變時(即<in Number>
),泛型的類型形參只能選取Number
自身以及其基類(超類型)。
當某方法中須要List<out Number>
類型的參數時,將<out Number>
轉換爲<? extends Number>
,表示泛型的類型形參能夠爲Number
自身以及其子類(子類型)。即List<Number>
協變的子類型集合有:List<Number>
、List<Int>
等。
List<Int>
在List<Number>
協變的子類型集合中。意味着當須要List<Number>
時,可使用List<Int>
來替換,List<Int>
是List<Number>
的子類型。符合協變的要求: Int
是 Number
的子類型,以至List<Int>
也是List<Number>
的子類型。
而若是協變的是List<Int>
,那麼將<out Int>
轉換爲<? extends Int>
。表示泛型的類型形參能夠爲Int
自身以及其子類(子類型)。即List<Int>
協變的子類型集合只有:List<Int>
。
List<Number>
不在List<Int>
協變的子類型集合中。意味着當須要List<Int>
時,不可使用List<Number>
來替換,List<Number>
不是List<Int>
的子類型。
這種思路對於逆變也是可行的。某方法中須要MutableList<in Number>
類型的參數時,將<in Number>
轉換爲<? super Number>
,表示泛型的類型形參能夠爲Number
自身以及其基類(超類型)。即MutableList<Number>
逆變的子類型集合有:MutableList<Number>
、MutableList<Any>
等。
MutableList<Int>
不在MutableList<Number>
逆變的子類型集合中。意味着當須要MutableList<Number>
時,不可使用MutableList<Int>
來替換,MutableList<Int>
不是MutableList<Number>
的子類型。
而若是逆變的是MutableList<Int>
,那麼將<in Int>
轉換爲<? super Int>
。表示泛型的類型形參能夠爲Int
自身以及其基類(超類型)。即MutableList<Int>
逆變的子類型集合有:MutableList<Int>
、MutableList<Number>
和 MutableList<Any>
。
MutableList<Number>
在MutableList<Int>
逆變的子類型集合中。意味着當須要MutableList<Int>
時,可使用MutableList<Number>
來替換,MutableList<Number>
是MutableList<Int>
的子類型。符合逆變的要求: Int
是 Number
的子類型,但MutableList<Number>
是MutableList<Int>
的子類型。
衆所周知,Kotlin中一個非泛型類有着對應的可空類型和非空類型,並且非空類型是可空類型的子類型。由於當須要可空類型的對象時,可使用非空類型的對象來替換。
關於可空類型和非空類型間的協變與逆變,也可使用剛纔的方法進行理解,只是此次再也不侷限於子類和父類,而是擴展到子類型和超類型。
<out A>
),泛型的類型形參只能選取A自身以及其子類型。<in A>
),泛型的類型形參只能選取A自身以及其超類型。 當某方法中須要List<out Any?>
類型的參數時,將<out Any?>
轉換爲<? extends Any?>
,表示泛型的類型形參能夠爲Any?
自身以及其子類型。即List<Any?>
協變的子類型集合有:List<Any?>
、List<Any>
等。
而若是逆變的是MutableList<Any?>
,那麼將<in Any?>
轉換爲<? super Any?>
。表示泛型的類型形參能夠爲Any?
自身以及其超類型。即MutableList<Any?>
逆變的子類型集合有:MutableList<Any?>
。
當你試圖將MutableList<Any>
作爲子類型傳遞給接收MutableList<in Any?>
類型參數的方法時,編譯器將報錯,編譯不經過。由於MutableList<Any?>
逆變的子類型集合中沒有MutableList<Any>
。
當某方法中須要List<out Any>
類型的參數時,將<out Any>
轉換爲<? extends Any>
,表示泛型的類型形參能夠爲Any
自身以及其子類型。即List<Any>
協變的子類型集合有:List<Any>
。
而若是逆變的是MutableList<Any>
,那麼將<in Any>
轉換爲<? super Any>
。表示泛型的類型形參能夠爲Any
自身以及其超類型。即MutableList<Any>
逆變的子類型集合有:MutableList<Any>
和 MutableList<Any?>
。
當你試圖將List<Any?>
作爲子類型傳遞給接收List<out Any>
類型參數的方法時,編譯器將報錯,編譯不經過。由於List<Any>
協變的子類型集合中沒有List<Any?>
。
到這裏或許有個疑問,我該依據什麼來選擇協變或者逆變呢?這就涉及關鍵字out
和in
的第二層含義了。
關鍵字out的兩層含義:
關鍵in的兩層含義:
out位置是指:該函數生產類型爲T
的值,泛型T只能做爲函數的返回值。而in位置是指:該函數消費類型T的值,泛型T做爲函數的形參類型。
Kotlin的型變聽從《Effective Java》中的 PECS (Producer-Extends, Consumer-Super)。只能讀取的對象做爲生產者,只能寫入的對象做爲消費者。
out
關鍵字使得一個類型參數協變:只能夠被生產而不能夠被消費。 out
修飾符確保類型參數 T 從 Iterator<T>
成員中返回(生產),並從不被消費。
public interface Iterator<out T> {
public operator fun next(): T
public operator fun hasNext(): Boolean
}
複製代碼
in
關鍵字使得一個類型參數逆變:只能夠被消費而不能夠被生產。 in
修飾符確保類型參數 T
從 Comparable<T>
成員中寫入(消費),並從不被生產。
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
複製代碼
配合協變分析,能夠清楚out
爲何扮演生產者角色:
List<Int>
、List<Long>
等子類型能夠替代List<Number>
,傳遞給接收List<Number>
類型的方法。而對外還是List<Number>
,但並不知道該泛型類實際的類型形參是什麼。Number
的任何子類型。但因爲不知道該泛型類實際的類型形參是什麼。對其進行寫入會形成類型不安全。(例如:可能接收的是一個List<Int>
,若是你對其寫入一個Long
,這時就會形成類型不安全。)List<Int>
,仍是List<Long>
等),返回(生產)的是Number
實例。以超類型的形式返回子類型實例,類型安全。 配合逆變分析,也能夠清楚in
爲何扮演消費者角色:
Consumer<Number>
、Consumer<Any>
等子類型能夠替代Consumer<Number>
,傳遞給接收Consumer<Number>
類型的方法。Consumer<Number>
(Consumer<Int>
、Consumer<Long>
等不能傳遞進來)。以超類型的形式消費子類型實例,類型安全。Number
呢,仍是Any
呢?)。只有使用Any
返回(生產)才能確保類型安全,因此讀取受限。(也就是說在逆變中,泛型 T
爲Number
時,你返回的不是Number
,而是Any
。) 那是否意味着out
關鍵字修飾的泛型參數是否是不能出如今in
位置 ?固然不是,只要函數內部能保證不會對泛型參數存在寫操做的行爲,可使用UnSafeVariance
註解使編譯器中止警告,就能夠將其放在in
位置。out
關鍵字修飾的泛型參數也是同理。
例如Kotlin的List
中contains
函數等,就是應用UnSafeVariance
註解使泛型參數存在於in位置,其內部沒有寫操做。
public interface List<out E> : Collection<E> {
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
public operator fun get(index: Int): E
public fun indexOf(element: @UnsafeVariance E): Int
public fun lastIndexOf(element: @UnsafeVariance E): Int
public fun listIterator(): ListIterator<E>
public fun listIterator(index: Int): ListIterator<E>
public fun subList(fromIndex: Int, toIndex: Int): List<E>
}
複製代碼
構造方法的參數既不在in
位置也不在out
位置。同時該位置規則只對對外公開的API有效。(即對private
修飾的函數無效)
協變 | 逆變 | 不變 | |
---|---|---|---|
結構 | Producer<out T> |
Consumer<in T> |
MutableList<T> |
Java實現 | Producer<? extends T> |
Consumer<? super T> |
MutableList<T> |
子類型化關係 | 保留子類型化關係 | 逆轉子類型化關係 | 無子類型化關係 |
位置 | out位置 | in位置 | in位置和out位置 |
角色 | 生產者 | 消費者 | 生產者和消費者 |
表現 | 只讀 | 只寫,讀取受限 | 便可讀也可寫 |
那麼使用泛型時,逆變、協變和不變如何選擇呢?
Array中存在又讀又寫的操做,若是爲其指定協變或逆變,都會形成類型不安全:
class Array<T>(val size: Int) {
fun get(index: Int): T { …… }
fun set(index: Int, value: T) { …… }
}
複製代碼
若是須要子類型化關係,則只讀操做(協變或不變)選擇協變,不然不變;只寫讀操做(逆變或不變),選擇逆變,不然不變。
Kotlin的型變分爲 聲明處型變 和 星點投射。所謂的星點投射就是使用 * 代替類型參數。表示你不知道關於泛型實參的任何信息,但仍然但願以安全的方式使用它。
Kotlin 爲此提供瞭如下星點投射的語法:
對於 Foo <T : TUpper>
,其中 T 是一個具備上界 TUpper
的不型變類型參數,Foo<*>
讀取值時等價於 Foo<out TUpper>
,而寫值時等價於 Foo<in Nothing>
。
對於 Foo <out T : TUpper>
,其中 T 是一個具備上界 TUpper
的協變類型參數,Foo <*>
等價於 Foo <out TUpper>
。 這意味着當 T
未知時,你能夠安全地從 Foo <*>
讀取 TUpper
的值。
對於 Foo <out T>
,其中 T 是一個協變類型參數,Foo <*>
等價於 Foo <out Any?>
。 由於 T
未知時,只有讀取 Any?
類型的元素是安全的。
對於 Foo <in T>
,其中 T
是一個逆變類型參數,Foo <*>
等價於 Foo <in Nothing>
。 由於 T
未知時,沒有什麼能夠以安全的方式寫入 Foo <*>
。
對於普通的 Foo <T>
,這其中沒有任何泛型實參的信息。Foo<*>
讀取值時等價於 Foo<out Any?>
,由於讀取 Any?
類型的元素是安全的;Foo<*>
寫入值是等價於Foo<in Nothing>
。
若是泛型類型具備多個類型參數,則每一個類型參數均可以單獨投影(以interface Function <in T, out U>
爲例):
能夠向MutableList<Any?>
中添加任何數據,但MutableList<*>只是通配某種類型,由於不知道其具體什麼類型,因此不容許向該列表中添加元素,不然會形成類型不安全。