Skip to content

逆变和协变

逆变与协变用来描述类型转换后的继承关系

协变:如果 A 是 B 的子类型,并且 Generic <A> 也是 Generic <B> 的子类型,那么 Generic <T> 可以称之为一个协变类。 逆变:如果 A 是 B 的子类型,并且 Generic <B>Generic <A> 的子类型,那么 Generic <T> 可以称之为一个逆变类。

协变与逆变关键在于类的父子关系在当类作为泛型参数时,泛型的父子关系是否有改变,父子关系保持协同一致的,叫协变,父子关系逆反了,叫逆变。

kotlin 中提供了两个修饰符:out:声明协变;in:声明逆变

有两种使用方式:1.在类或接口的定义处声明、2.使用处声明

  1. 在类或接口的定义处声明
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.
}
  1. 使用处声明
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)。之所以要有这样的规定,主要原因是:子类对象可以当作父类对象使用,反之不行。

Released under the MIT License.