关于kotlin泛型中的协变和逆变的个人理解

-2021.8.26
带有主观性,理性观看,客观评价

WARNING:本文章适合了解一点协变和逆变但又不完全懂的人看一看

泛型协变:如果定义了一个MyClass< T >的泛型类,其中A是B的子类型,同时MyClass< A >又是MyClass< B >的子类型,那么就可以称MyClass在T这个泛型上是协变

错误写法:

open class Dad()

class Son(): Dad()

class Generics<out T> {
    private var data: T?=null
    fun get(): T?{
        return data
    }
    fun set(t: T?){
        data = t
    }
}

fun function(generics: Generics<Dad>){
    val dad = Dad()
    generics.set(dad)
}

fun main(){
    val sonGenerics = Generics<Son>()
    function(sonGenerics)
}

正确写法:

open class Dad()

class Son(): Dad()

class Generics<out T> {
    private var data: T?=null
    fun get(): T?{
        return data
    }
}

fun function(generics: Generics<Dad>){
    val dad: Dad? = generics.get()
}

fun main(){
    val sonGenerics = Generics<Son>()
    function(sonGenerics)
}

这边我定义了一个Generics泛型类,一个Dad父类,一个Son子类,先来说说为什么这样写是对的。
我这时候已经声明了T是只读的,那么不存在出现在in位置上的情况。
根据定义A是B的子类型,MyClass< A >就是是MyClass< B >的子类型,即使function需要的是一个泛型是Dad的Generics类,但Generics< Son >是Generics< Dad >的子类,就可以将参数传递进去,get方法就可以使用了,这个协变是很好理解的,就和多态差不多的道理。

注:generics.get()得到的是一个Son实例,但是被一个Dad类的dad引用接受了,这是完全可行的,因为多态,父类引用指向子类

再来说说错误写法错在哪,在这时候,我在Generics类中添加了set方法,如果我在function参数列表中传入一个Generics,这时候再调用它的set方法传入一个Dad实例,那么会出现类型转换异常(泛型指定Son而实例为Dad,Dad无法向下转型),这种行为是不安全的。

------------------------------------------分割线------------------------------------------

泛型逆变:如果定义了一个MyClass< T >的泛型类,其中A是B的子类型,同时MyClass< B >又是MyClass< A >的子类型,那么我们就可以称MyClass在T上是逆变的

错误写法:

open class Dad()

class Son(): Dad()

class Generics<in T> {
    private var data: T?=null
    fun get(): T?{
        return data
    }
    fun set(t: T?){
        data = t
    }
}

fun function(generics: Generics<Son>){
    val value: Son? = generics.get()
}

fun main(){
    val dadGenerics = Generics<Dad>()
    function(dadGenerics)
}

正确写法:

open class Dad()

class Son(): Dad()

class Generics<in T> {
    private var data: T?=null
    fun set(t: T?){
        data = t
    }
}

fun function(generics: Generics<Son>){
    val son = Son()
    generics.set(son)
}

fun main(){
    val dadGenerics = Generics<Dad>()
    function(dadGenerics)
}

同样的我们先来看看为什么正确写法为什么正确,function指定的参数是Generics类型,那么这时传入一个Generics类型的参数,因为声明了in,T是只写的,所以根据定义A是B的子类型,MyClass< B >就是MyClass< A >的子类型,Generics传入function中,由于Generics是Generics的子类型,那么这个参数传入是正确的,function内部调用set方法,为传入的dadGenerics赋值,此时又可以联想到多态,在Generics中设置一个Son类型的实例,这是完全成立的,不会出现类型转换异常

那么错在哪?如果在function中调用get方法,那么由于实际传入的参数是Generics类型的,会获取到Dad实例,然而function中参数指定的是Generics类型,那么编译器就会认为get得到的就是一个Son类型的实例(我显式声明了),又因为Dad实例不能向下转型为Son实例,所以这样会导致类型转换异常。

至于为什么如此设计,暂时立个flag,以后会进行更新

由于作者没有先了解Java的协变和逆变直接就学了kotlin的协变和逆变,可能理解有偏差,请谅解

-2021.11.16
关于java泛型擦除机制,墙裂推荐一篇文章
Java泛型类型擦除以及类型擦除带来的问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值