• 从底层实现剖析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这样的语言关于这些概念就很轻松了。

  • 相关阅读:
    PHP函数篇详解十进制、二进制、八进制和十六进制转换函数说明
    TP5安装workerman版本的坑
    下载git2.2.1并将git添加到环境变量中
    RedHat安装git报错 expected specifier-qualifier-list before ‘z_stream’
    Git出现fatal: Unable to find remote helper for 'https'
    ThinkPHP5实现定时任务
    php一行代码获取本周一,本周日,上周一,上周日,本月一日,本月最后一日,上月一日,上月最后一日日期
    git 查看日志记录
    程序员必读之软件架构 读书笔记
    centos7 安装桌面
  • 原文地址:https://www.cnblogs.com/webor2006/p/11294849.html
Copyright © 2020-2023  润新知