Kotlin 泛型協(xié)變與逆變的理解

協(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寫入對象,因為productionAProduction<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)系相反,所以叫逆變

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容