協(xié)變與逆變定義
逆變與協(xié)變用來描述類型轉(zhuǎn)換后的繼承關(guān)系
協(xié)變:如果 A 是 B 的子類型,并且Generic<A> 也是 Generic<B> 的子類型,那么 Generic<T> 可以稱之為一個協(xié)變類。
逆變:如果 A 是 B 的子類型,并且 Generic<B> 是 Generic<A> 的子類型,那么 Generic<T> 可以稱之為一個逆變類。
從上面的定義我們很好理解,協(xié)變與逆變關(guān)鍵在于類的父子關(guān)系在當類作為泛型參數(shù)時,泛型的父子關(guān)系是否有改變,父子關(guān)系保持協(xié)同一致的,叫協(xié)變,父子關(guān)系逆反了,叫逆變。
協(xié)變與逆變表示
kotlin 中提供了兩個修飾符:out:聲明協(xié)變;in:聲明逆變
有兩種使用方式:1.在類或接口的定義處聲明、2.使用處聲明
在類或接口的定義處聲明,比如:
//A是父類,B為A的子類
open class A
class B: A(){}
//out聲明協(xié)變
interface Production<out T> {
//類的參數(shù)類型使用了out之后,該參數(shù)只能出現(xiàn)在方法的返回類型
fun produce(): T
}
//in聲明逆變
interface Consumer<in T> {
//類的參數(shù)類型使用了in之后,該參數(shù)只能出現(xiàn)在方法的入?yún)? fun consume(item: T)
}
class ProductionA:Production<A> {
override fun produce(): A {
return A()
}
}
class ProductionB:Production<B> {
override fun produce(): B {
return B()
}
}
class ConsumerA:Consumer<A> {
override fun consume(item: A) {
}
}
class ConsumerB:Consumer<B> {
override fun consume(item: B) {
}
}
fun main() = runBlocking {
//Production<out T>是一協(xié)變類,因為B是A的子類,則Production<B>相當于是Production<A>子類
var productionA:Production<A> = ProductionB()
//下面的賦值則是錯誤的
//var productionB:Production<B> = ProductionA() //error 報 Type mismatch.
//Consumer<T>是一逆變類,因為B是A的子類,則Consumer<A>相當于Consumer<B>的子類
var consumerB:Consumer<B> = ConsumerA() //相當于子類對象賦給父類變量
//下面的賦值則是錯誤的
//var consumerA:Consumer<A> = ConsumerB() //error 報Type mismatch.
}
在使用處聲明,比如:
//A是父類,B為A的子類
open class A
class B: A(){}
fun main() = runBlocking {
val listB = mutableListOf(B())
//error 報Type mismatch. Required:MutableList<A> Found:kotlin.collections.ArrayList<B>
//val listA:MutableList<A> = listB
//加入out后,表示協(xié)變,MutableList<B>作為MutableList<A>子類,但只取出元素,無法插入元素
val listA:MutableList<out A> = listB //ok
listB.add(B())
//listA實際對應的對象是ArrayList<B>,只能取出
val a:A = listA[0]
//下面兩行都是錯誤
//listA.add(A()) //error 報:Type mismatch. Required:Nothing Found:A
//listA.add(B()) //error 報:Type mismatch. Required:Nothing Found:B
val listA2:MutableList<A> = mutableListOf(A())
//val listB2:MutableList<B> = listA2 //報錯
//加入out后,表示逆變,MutableList<A>作為MutableList<B>子類,可插入元素,但取出時類型為 Any?
val listB2:MutableList<in B> = listA2 //ok
listB2.add(B())
//下面報錯
//val b:B = listB2[0] //error 報:Type mismatch. Required: B Found: Any?
val b = listB2[0] //將類型去掉,是可以的
}
使用out/in時的規(guī)則
當一個類 C 的類型參數(shù) T 被聲明為 out 時,那么就意味著類 C 在參數(shù) T 上是協(xié)變的;參數(shù) T 只能出現(xiàn)在類 C 的輸出位置,不能出現(xiàn)在類 C 的輸入位置。
當一個類 C 的類型參數(shù) T 被聲明為 in 時,那么就意味著類 C 在參數(shù) T 上是逆變的;參數(shù) T 只能出現(xiàn)在類 C 的輸如位置,不能出現(xiàn)在類 C 的輸出位置。
| 關(guān)鍵字 | 功能 | 使用時聲明 | 類(接口)定義時聲明 |
|---|---|---|---|
| out | 協(xié)變 | 只能讀取不能寫入泛型對象 | 泛型參數(shù)只能出現(xiàn)在輸出位置 |
| in | 逆變 | 只能寫入,不能按照泛型類型讀取泛型對象 | 泛型參數(shù)只能出現(xiàn)在輸入位置 |
協(xié)變與逆變理解
out與in分別表示協(xié)變與逆變,已經(jīng)從字面上說明了其規(guī)則,即協(xié)變時只能輸出(out),逆變時只能輸入(in)。之所以要有這樣的規(guī)定,主要原因是:子類對象可以當作父類對象使用,反之不行。
普通的類,我們很好理解:
//A是父類,B為A的子類
open class A
class B: A(){}
fun main() = runBlocking {
val a:A = B() //a的類型是父類,實際上是子類對象
}
換成泛型:
//A是父類,B為A的子類
open class A
class B: A(){}
//out聲明協(xié)變
interface Production<out T> {
//類的參數(shù)類型使用了out之后,該參數(shù)只能出現(xiàn)在方法的返回類型
fun produce(): T
}
class ProductionA:Production<A> {
override fun produce(): A {
return A()
}
}
class ProductionB:Production<B> {
override fun produce(): B {
return B()
}
}
fun main() = runBlocking {
//泛型會存在兩對父子關(guān)系:1.泛型類的父子關(guān)系 2.泛型參數(shù)類的父子關(guān)系
val productionA:Production<A> = ProductionB()
}
最重要的一點,操作productionA對象時,因為實際是Production<B>類型的對象,所以:
1.從productionA取出來的對象是B,而productionA表示A類型,B可以作為A來使用,符合子類對象可以當作父類對象使用的原則,沒有問題
2.假設可以從productionA寫入對象,因為productionA是Production<A>類型,所以寫入的是A對象,這樣會將A對象寫入Production<B>中,即會出現(xiàn)將父類對象當成子類對象使用,就會出問題
從上面的兩點可以知道,為了維持泛型參數(shù)類的父子關(guān)系滿足子類對象可以當作父類對象使用的原則,只能生成子類對象,所以協(xié)變時只能輸出元素,不能輸入元素(輸入元素意味著會產(chǎn)生父類對象)
逆變的情況:
//Consumer<T>是一逆變類,因為B是A的子類,則Consumer<A>相當于Consumer<B>的子類
var consumerB:Consumer<B> = ConsumerA() //相當于子類對象賦給父類變量
上面如果要滿足類對象可以當作父類對象使用的原則,只能生成B類型的對象,所以只能輸入元素,不能輸出元素
生成對象的類型
out與in其實表示了生成泛型參數(shù)對象的出處,以泛型為主體,out就是從泛型取出對象,in就是從外面生成新的對象,放入泛型之中。
從泛型取出對象的類型比較好理解,取出來的肯定的實際的泛型參數(shù),那從外面生成新的對象是什么類型呢,應該是定義變量時所聲明的類型,舉個例子:
val list:MutableList<A> = ArrayList()
list.add(A())
因為list定義時的泛型參數(shù)類型是A,所以從外面in對象時,插入應該是A的對象,假設list的實際所指向的對象可以為MutableList<B>類型,list.add的類型也應該為A,因為list所插入的對象類型是以它定義時的類型為依據(jù),而不是實際所指向的對象。
再與之前說的只能生成子類對象的原則結(jié)合起來,不難得出:
泛型參數(shù)是子類的泛型對象可以賦值給泛型參數(shù)是父類的泛型變量的條件是泛型之中只能取出對象,所以用out約束,泛型的父子關(guān)系與泛型參數(shù)的父子關(guān)系一致,所以叫協(xié)變
泛型參數(shù)是父類的泛型對象可以賦值給泛型參數(shù)是子類的泛型變量的條件是泛型之中只能傳入對象,所以用in約束,泛型的父子關(guān)系與泛型參數(shù)的父子關(guān)系不一致,所以叫逆變
總結(jié)
1.只能生成子類對象
2.生成子類對象有兩個途徑,從泛型取出,從泛型外部new產(chǎn)生
3.從泛型取出的是實際的對象,從外部new的的定義時的類型變量
4.out表示取出子類對象,只能為實際對象,與泛型本身的父子關(guān)系一致,所以叫協(xié)變
5.in表示從外部new子類對象,只能為定義時的類型變量,與泛型本身的父子關(guān)系相反,所以叫逆變