从底层实现剖析Kotlin协变与逆变的原理

继续还是探究协变与逆变,在正式开始之前,先来对Kotlin和Java的协变与逆变进行一个对比:

1、Kotlin是声明处协变;而在Java中是在使用处协变:

如何理解,我们先来回顾一下在Java使用协变的写法:

很显然是在我们使用的时候进行协变的,而在Kotlin中:

2、Kotlin中的out关键字叫做variance annotation,因为它是在类型参数声明处所指定的,因此我们称之为声明处协变(declaration-site variance);而对Java来说则是使用处协变(use-site variance),其中类型通配符使得类型协变成为可能。

另外需要注意:对于Java的这个泛型声明不要跟协变搞混了,如下:

它跟Java的使用处协变是完全不同的概念,使用协变一定是<? extends xxxx>。

好,接一来再来以一个完整的例子进一步巩固Kotlin的协变、逆变、不变的知识点,如下:

接下来定义逆变:

接下来还有一种不变情况,也就是该泛型会被作为参数的输入和输出类型,如下:

其中咱们如果对这个不变进行调整,就能真切感爱到协变与逆变的使用场景了:

如果是声明成协变,则只能读,如果声明成逆变,则只能写。

好,继续,接下来再定义三个类:

接下来则定义协变类:

package com.kotlin.test2

/**
 * 如果泛型类只是将泛型类型作为其方法的输出类型,那么我们就可以使用out
 *
 * produce = output = out
 */
interface Producer<out T> {

    fun produce(): T

}

/**
 * 如果泛型类只是将泛型类型作为其方法的输入类型,那么我们就可以使用in
 *
 * consumer = intput = in
 */
interface Consumer<in T> {

    fun consumer(item: T)

}

/**
 * 如果泛型类同时将泛型类型作为其方法的输入类型与输出类型,那么我们就不能使用out与in来修饰泛型
 */
interface ProducerConsumer<T> {
    fun produce(): T

    fun consumer(item: T)
}

open class Fruit

open class Apple: Fruit()

class ApplePear: Apple()

class FruitProducer: Producer<Fruit> {
    override fun produce(): Fruit {
        println("Produce Fruit")

        return Fruit()
    }

}

class AppleProducer: Producer<Apple> {
    override fun produce(): Apple {
        println("Produce Apple")

        return Apple()
    }

}

class ApplePearProducer: Producer<ApplePear> {
    override fun produce(): ApplePear {
        println("Produce ApplePear")

        return ApplePear()
    }

}

下面来使用一下:

下面来理解一下标红的代码,每一句比较好理解,因为是Fruit类型:

接下来解决第二句,第二句理解了,第三句也就理解了,它返回的类型是Apple:

我们可以调用一下producer2.produce()方法,看一下返回值:

本来返回的是Fruit类型,而我们在里面返回的真正类型是Apple类型:

根据多态,这种返回肯定是没任何问题。 如果我们修改成这样就不行了:

接下来再来使用逆变:

package com.kotlin.test2

/**
 * 如果泛型类只是将泛型类型作为其方法的输出类型,那么我们就可以使用out
 *
 * produce = output = out
 */
interface Producer<out T> {

    fun produce(): T

}

/**
 * 如果泛型类只是将泛型类型作为其方法的输入类型,那么我们就可以使用in
 *
 * consumer = intput = in
 */
interface Consumer<in T> {

    fun consume(item: T)

}

/**
 * 如果泛型类同时将泛型类型作为其方法的输入类型与输出类型,那么我们就不能使用out与in来修饰泛型
 */
interface ProducerConsumer<T> {
    fun produce(): T

    fun consume(item: T)
}

open class Fruit

open class Apple: Fruit()

class ApplePear: Apple()

class FruitProducer: Producer<Fruit> {
    override fun produce(): Fruit {
        println("Produce Fruit")

        return Fruit()
    }

}

class AppleProducer: Producer<Apple> {
    override fun produce(): Apple {
        println("Produce Apple")

        return Apple()
    }

}

class ApplePearProducer: Producer<ApplePear> {
    override fun produce(): ApplePear {
        println("Produce ApplePear")

        return ApplePear()
    }

}

fun main(args: Array<String>) {
    //对于"out"泛型来说,我们可以将子类型对象赋给父类型引用
    var producer1: Producer<Fruit> = FruitProducer()
    var producer2: Producer<Fruit> = AppleProducer()
    var producer3: Producer<Fruit> = ApplePearProducer()
}

class Human: Consumer<Fruit> {
    override fun consume(item: Fruit) {
        println("Consume Fruit")
    }

}

class Man: Consumer<Apple> {
    override fun consume(item: Apple) {
        println("Consume Apple")
    }

}

class Boy: Consumer<ApplePear> {
    override fun consume(item: ApplePear) {
        println("Consume ApplePear")
    }

}

接下来则来使用一下:

好,接下来理解一下:

当我们调用consumer1.consume()方法时,本应该是要传ApplePear类型:

但是真实需要的类型是Fruit类型,如下:

这不还是多态的应用么,一个子类对象赋值给父类对象嘛,所以,协变和逆变的根源其实就是多态在起着关键作用。所以至此咱们对于协变与逆变的了解又更加深入了,这个概念是非常之重要的,了解透了之后对于学习像scala这样的语言关于这些概念就很轻松了。

posted on 2019-08-03 14:56  cexo  阅读(889)  评论(0编辑  收藏  举报

导航