Kotlin泛型、协变、逆变

类(Class) 与类型(Type)

Kotlin 中类和类型是不一样的概念,如下图:


型变

型变是指类型转换后的继承关系。Kotlin 的型变分为逆变、协变和不变。

Kotlin 和 Java 的协变

在 Java 中用通配符 ? extends T 表示协变,extends 限制了父类型 T。
在 Kotlin 中关键字 out T 表示协变,含义和 Java 一样

// kotlin
val numbers: MutableList<out Number> = ArrayList<Int>()

// Java
List<? extends Number> numbers = new ArrayList<Integer>();

协变通配符 ? extends Number 或者 out Number 表示接受 Number 或者 Number 子类型为对象的集合,协变放宽了对数据类型的约束。

但是放宽是有代价的,调用 add() 方法会编译失败,虽然协变放宽了对数据类型的约束,可以接受 Number 或者 Number 子类型为对象的集合,但是代价是 无法添加元素,只能获取元素,因此协变只能作为生产者,向外提供数据。

// Koltin
val numbers: MutableList<out Number> = ArrayList<Int>()
numbers.add(1)

// Java
List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(1)

因为 ? 表示未知类型,所以编译器也不知道会往集合中添加什么类型的数据,因此索性不允许往集合中添加元素。

但是如果想让上面的代码编译通过,想往集合中添加元素,这就需要用到逆变了。

Kotlin 和 Java 的逆变

在 Java 中用通配符 ? super T 表示逆变,其中 ? 表示未知类型,super 主要用来限制未知类型的子类型 T,比如 ? super Number,只要声明时传入是 Number 或者 Number 的父类型都可以
在 Kotlin 中关键字 in T 表示逆变,含义和 Java 一样

逆变的例子:

// Kotlin
val numbers: MutableList<in Number> = ArrayList<Number>()
numbers.add(100)
val item: Int = numbers.get(0)

// Java
List<? super Number> numbers = new ArrayList<Number>();
numbers.add(100);
int item = numbers.get(0);

逆变通配符 ? super Number 或者关键字 in 将继承关系颠倒过来,主要用来限制未知类型的子类型,在上面的例子中,编译器知道子类型是 Number,因此只要是 Number 的子类都可以添加。add是可以添加成功,但是get无法添加成功。

实战代码:
//泛型类定义
class Generic<T> (private val obj: T) {  //需求: 万能输入器

    fun show() = println("输出:" + obj)
}

data class Student(val name: String, val age: Int, val sex: Char)
data class Teacher(val name: String, val age: Int, val sex: Char)

fun showGeneric() {
    val stu1 = Student("张三", 10 ,'0')
    val stu2 = Student("李四", 10 ,'0')
    val tea1 = Teacher("miss", 25, '1')

    Generic(stu1).show()
    Generic(stu2).show()
    Generic(tea1).show()
    Generic(String("lili".toByteArray())).show()
}


class Dinamic<T> (vararg objects: T) { //定义泛型的动态参数

    //out 我们的T只能被读取,不能被修改
    val objectArray: Array<out T> = objects //因为T为任意类型,需要集合承接,需要定义一个下界,即协变

    fun showObj(index: Int) : T ? = objectArray[index].takeIf { it is Int }
}

fun showVararg() {
    Dinamic(10, "haha", Student("", 1, '0')).showObj(0)
}


// Out In

//生产者  out T 协变  使用out T 数据就是只读的,不能被修改
interface Producer<out T> {

//    fun consumer(item: T)  //进行消费
//        //这里是out修饰T,无法对T对象进行操作使用,所以编译不通过

    fun producer(): T
}

//消费者 in T 逆变  使用in T 数据就是只读的,不能被修改
interface Consumer<in T> {
    fun consumer(item: T)

    //不能被读取 (编译不通过)
//    fun producer(): T
}

//生产者 & 消费者 T 默认情况下,是不变
interface ProducerAndConsumer <T> {
    fun consumer(item: T) //能被读取

    fun producer(): T //能被修改
}

open class Animal
open class Human: Animal()
class ProduceClass: Producer<Animal> {

    override fun producer(): Animal {
        println("生产者 Animal")
        return Animal()
    }
}
class ProduceClass2: Producer<Human> {

    override fun producer(): Human {
        println("生产者 Human")
        return Human()
    }
}

class ComsumerClass: Consumer<Animal> {

    override fun consumer(item: Animal) {
        println("消费者 Animal")
    }
}
class ComsumerClass2: Consumer<Human> {

    override fun consumer(item: Human) {
        println("消费者 Human")
    }
}

fun showOutIn() {
    //泛型默认情况是:泛型的子类对象 不可以赋值给 泛型的父类对象
//    List<CharSequence> list2 = new ArrayList<String>() //报错
//    List<? extends CharSequence> list2 = new ArrayList<String>() //正确 声明泛型是CharSequence或它的子类就可以


    val p1: Producer<Animal> = ProduceClass()
    val p2: Producer<Animal> = ProduceClass2() // ProduceClass2传递的是Human,因为 T是out修饰,对应java的 ? extend T,只要类型是Animal的子类都可以
    p2.producer()

    val c1: Consumer<Human> = ComsumerClass() //ComsumerClass传递的事Animal, 因为 T是in修饰,对应java的 ? super T,只要类型是Human的父类都可以
    c1.consumer(Human())
    val c2: Consumer<Human> = ComsumerClass2()
}

参考:
https://zhuanlan.zhihu.com/p/531581456
https://www.jianshu.com/p/056d53e699ce

Github代码地址:

https://github.com/running-libo/KotlinPractise

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容