秒懂Kotlin之彻底弄懂形变注解out与in

本文详细解析了Kotlin中的泛型协变与逆变,通过out和in关键字展示了它们在方法参数和接口声明中的应用,帮助读者深入理解这两种概念,并通过实例代码进行演示。同时,文章提到了Java中泛型和数组的对比,强调了Kotlin在此处的设计考虑。
摘要由CSDN通过智能技术生成

[版权申明] 非商业目的注明出处可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/108759370
出自:shusheng007

文章首发于个人博客

概述

本文承接于上一篇:秒懂Kotlin之协变(Covariance)逆变(Contravariance)与抗变(Invariant)一定要先阅读这一篇文章,再阅读本文,不然看不懂!

上篇讲到Java中泛型是抗变的,但是数组却是协变的。Kotlin做的更彻底,不仅泛型是抗变的就连数组也变成抗变的了。

下面的代码是编译不过的

val strArray:Array<String> = arrayOf("shu","sheng","007")
val array:Array<Any> = strArray

报错:

Type mismatch: inferred type is Array<String> but Array<Any> was expected

泛型型变

官方文档见:Variance

Kotlin中没有通配符,取而代之的是 Declaration-site varianceUse-site variance 。其通过两个关键字outin来实现Java中的? extends? super的功能.

假设我们有如下两个类和一个接口

open class Animal
class Dog : Animal()

interface Box<T> {
    fun getAnimal(): T
    fun putAnimal(a: T)
}

协变(out)

我们要定义一个方法,参数类型为Box<Animal>,但是我们希望可以传入Box<Dog>即希望可以发生协变

Java实现

private Animal getOutAnimalFromBox(Box<? extends Animal> box) {
   Animal animal = box.getAnimal();
//       box.putAnimal(? ) 没有办法调用修改方法,因为我们不知道?究竟是一个什么类型,没办法传入
   return animal;
}

Kotlin对应的实现为:

fun getAnimalFromBox(b: Box<out Animal>) : Animal {
    val animal: Animal = b.getAnimal()
//    b.putAnimal(Nothing) 无法调用,因为方法需要一个Nothing类型的对象,但是在kotlin中无法获取
    return animal
}

此方法可以接受Box<Dog>类型的参数了。

可见此处使用out 代替了? extends。从结果来看确实更合适一点,因为传入的参数只能提供值,而不能消费值。由于out是在方法调用的参数中标记的,处于使用端,所以叫Use-site varianceUse-site variance对应的就是Declaration-site variance了。

我们发现接口Box<T>中既有消费值的方法fun putAnimal(a: T),又有提供值的方法fun getAnimal(): T,导致我们必须在使用侧告诉编译器我们要使用哪一类方法。那我们可以在声明接口的时候告诉编译器吗?答案是肯定的,但是就需要将接口拆分为只包含提供值的方法的接口producer与只包含消费值的方法的接口consumer

//producer
interface ReadableBox<out T> {
    fun getAnimal(): T
}
//consumer
interface WritableBox<in T> {
    fun putAnimal(a: T)
}

拆分完接口并做了相应的声明后,就可以不在使用端使用out或者in了。

fun getAnimalFromReadableBox(b: ReadableBox<Animal>){
    val a: Animal = b.getAnimal()
}

上面的方法可以直接接受ReadableBox<Dog>类型的参数,给人的感觉好像是Kotlin使得泛型协变了。

getAnimalFromReadableBox(object :ReadableBox<Dog>{
    override fun getAnimal(): Dog {
        return Dog()
    }
})

此种情况下outin是在声明时候使用的,所以叫Declaration-site variance了。

逆变(in)

我们要定义一个方法,参数类型为Box<Dog>,但是我们希望可以传入Box<Animal>,即希望可以发生逆变

Java实现

private void putAnimalInBox(BoxJ<? super Dog> box){
    box.putAnimal(new Dog());
    Object animal= box.getAnimal();// 可以调用读取方法,但是返回的类型确实Object,因为我们只能确定?的大基类是Object
}

Kotlin对应实现

fun putAnimalInBox(b: Box<in Dog>){
    b.putAnimal(Dog())
    val animal:Any? = b.getAnimal()// 可以调用读取方法,但是返回的类型确实Any?,因为我们只能确定?的大基类是Any?
}

此方法可以接受Box<Animal>类型的参数了

可见此处使用in 代替了? super,从结果来看确实更合适一点,因为传入的参数只适合消费值,而不适合获取值,获取到的值失去了有用的类型信息。由于in是在方法调用的参数中标记的,处于使用端,所以叫Use-site variance

让我们来看一下使用Declaration-site variance实现逆变

fun putAnimalToWritableBox(b:WritableBox<Dog>){
    b.putAnimal(Dog())
}

上面的方法可以直接接受WritableBox<Animal>类型的参数,给人的感觉好像是Kotlin使得泛型逆变了。

putAnimalToWritableBox(object :WritableBox<Animal>{
    override fun putAnimal(a: Animal) {
    }
})

总结

kotlin中令初学者费解的outin关键字就彻底将完了,相信你应该秒懂了。如果还是不懂,说明你太早看到这篇文章拉,建议你先收藏,以后等水平提高了再回来看看。

对了,记得点赞,分享。

暮云收尽溢清寒,银汉无声转玉盘。此生此夜不长好,明月明年何处看。《阳关曲 中秋月》苏轼

参考文章:

  1. Generics
  2. The Ins and Outs of Generic Variance in Kotlin
  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShuSheng007

亲爱的猿猿,难道你又要白嫖?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值