Kotlin的泛型逆变与协变

600 阅读2分钟

跟Java的泛型一样,Kotlin的泛型也是用来限制其类型

class Test<T: CallBack> {
   fun add(t: T){
      t.run()
      t.callback()
   }
}

// 如果T继承了多个父类则使用where关键字
// where T:CallBack, T:Runnable表示T既是CallBack的子类又是Runnable的子类
class Test<T> where T:CallBack, T:Runnable{
   fun add(t: T){
      t.run()
      t.callback()
   }
}

reified真泛型

默认泛型是不支持访问泛型的类型的。使用reified关键字,才能支持访问泛型的类型 。另外,带有reified真泛型的函数必须是inline内联函数,这个比较好理解,inline将函数的代码拷贝到调用的地方,才能知道具体泛型的类型。

//带有reified的函数必须为内联函数 
inline fun <reified T : Activity> Activity.start() {
    // T::class.java T为真泛型 才能访问JVM类对象
    startActivity(Intent(this, T::class.java));
}
inline fun <reified T> printIfTypeMatch(item: Any) {
    if (item is T) { // 👈 这里就不会在提示错误了
        println(item)
    }
}

协变out与逆变in

Kotlin的协变与逆变跟Java的原理是一样。唯一的区别就是用关键字不同。

  • Koltin使用关键字out来支持协变,等同于Java的上界通配符? extends 包括AA的子类

  • Kotlin使用关键字in来支持逆变,等同于Java的下界通配符? super 包括AA的父类

协变 out 表示,out A表示是其类型A的子类,其类型不确定,不支持修改,只能读取到基类A

裂变in 就反过来,in A表示是A的父类。基类不确定,不支持get。但是A一定是这个未知类型的子类型,根据多态性,支持修改

// out
class Producer<T> {
    fun produce(): T? {
        return null
    }
}

val producer: Producer<out TextView> = Producer<Button>()
val textView: TextView? = producer.produce()

// in
class Consumer<T> {
    fun consume(t: T) {
        
    }
}

val consumer: Consumer<in Button> = Consumer<TextView>()
consumer.consume(Button(context)) // 👈 相当于 'List' 的 'add'

在前面的例子中,在声明 Producer 的时候已经确定了泛型 T 只会作为输出来用,但是每次都需要在使用的时候加上 out TextView 来支持协变,写起来很麻烦。

Kotlin 提供了另外一种写法:可以在声明类的时候,给泛型符号加上 out 关键字,表明泛型参数 T 只会用来输出,在使用的时候就不用额外加 out 了。

class Producer<out T> {
    fun produce(): T? {
        return null
    }
}

val producer: Producer<TextView> = Producer<Button>() // 👈 这里不写 out 也不会报错
val textView: TextView? = producer.produce()