[版权申明] 非商业目的注明出处可自由转载
博文地址: http://app.shusheng007.top/2020/09/23/2/
出自: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 variance和Use-site variance 。其通过两个关键字out
和in
来实现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 variance与Use-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()
}
})
此种情况下out
和in
是在声明时候使用的,所以叫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中令初学者费解的out
与in
关键字就彻底将完了,相信你应该秒懂了。如果还是不懂,说明你太早看到这篇文章拉,建议你先收藏,以后等水平提高了再回来看看。
对了,记得点赞,分享。
暮云收尽溢清寒,银汉无声转玉盘。此生此夜不长好,明月明年何处看。《阳关曲 中秋月》苏轼
参考文章:
文章评论