逆变和协变
逆变与协变用来描述类型转换后的继承关系
协变:如果 A 是 B 的子类型,并且
Generic <A>
也是Generic <B>
的子类型,那么Generic <T>
可以称之为一个协变类。 逆变:如果 A 是 B 的子类型,并且Generic <B>
是Generic <A>
的子类型,那么Generic <T>
可以称之为一个逆变类。
协变与逆变关键在于类的父子关系在当类作为泛型参数时,泛型的父子关系是否有改变,父子关系保持协同一致的,叫协变,父子关系逆反了,叫逆变。
kotlin 中提供了两个修饰符:out:声明协变;in:声明逆变
有两种使用方式:1.在类或接口的定义处声明、2.使用处声明
- 在类或接口的定义处声明
kotlin
//A是父类,B为A的子类
open class A
class B: A(){}
//out声明协变
interface Production<out T> {
//类的参数类型使用了out之后,该参数只能出现在方法的返回类型
fun produce(): T
}
//in声明逆变
interface Consumer<in T> {
//类的参数类型使用了in之后,该参数只能出现在方法的入参
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>是一协变类,因为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.
}
- 使用处声明
kotlin
//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后,表示协变,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时的规则 当一个类 C 的类型参数 T 被声明为 out 时,那么就意味着类 C 在参数 T 上是协变的;参数 T 只能出现在类 C 的输出位置,不能出现在类 C 的输入位置。
当一个类 C 的类型参数 T 被声明为 in 时,那么就意味着类 C 在参数 T 上是逆变的;参数 T 只能出现在类 C 的输如位置,不能出现在类 C 的输出位置。
关键字 功能 使用时声明 类(接口)定义时声明 out 协变 只能读取不能写入泛型对象 泛型参数只能出现在输出位置 in 逆变 只能写入,不能按照泛型类型读取泛型对象 泛型参数只能出现在输入位置 协变与逆变理解 out与in分别表示协变与逆变,已经从字面上说明了其规则,即协变时只能输出(out),逆变时只能输入(in)。之所以要有这样的规定,主要原因是:子类对象可以当作父类对象使用,反之不行。