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

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

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

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

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

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

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

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

接下来定义逆变:

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

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

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

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

接下来则定义协变类:

  1. package com.kotlin.test2
  2.  
  3. /**
  4. * 如果泛型类只是将泛型类型作为其方法的输出类型,那么我们就可以使用out
  5. *
  6. * produce = output = out
  7. */
  8. interface Producer<out T> {
  9.  
  10. fun produce(): T
  11.  
  12. }
  13.  
  14. /**
  15. * 如果泛型类只是将泛型类型作为其方法的输入类型,那么我们就可以使用in
  16. *
  17. * consumer = intput = in
  18. */
  19. interface Consumer<in T> {
  20.  
  21. fun consumer(item: T)
  22.  
  23. }
  24.  
  25. /**
  26. * 如果泛型类同时将泛型类型作为其方法的输入类型与输出类型,那么我们就不能使用out与in来修饰泛型
  27. */
  28. interface ProducerConsumer<T> {
  29. fun produce(): T
  30.  
  31. fun consumer(item: T)
  32. }
  33.  
  34. open class Fruit
  35.  
  36. open class Apple: Fruit()
  37.  
  38. class ApplePear: Apple()
  39.  
  40. class FruitProducer: Producer<Fruit> {
  41. override fun produce(): Fruit {
  42. println("Produce Fruit")
  43.  
  44. return Fruit()
  45. }
  46.  
  47. }
  48.  
  49. class AppleProducer: Producer<Apple> {
  50. override fun produce(): Apple {
  51. println("Produce Apple")
  52.  
  53. return Apple()
  54. }
  55.  
  56. }
  57.  
  58. class ApplePearProducer: Producer<ApplePear> {
  59. override fun produce(): ApplePear {
  60. println("Produce ApplePear")
  61.  
  62. return ApplePear()
  63. }
  64.  
  65. }

下面来使用一下:

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

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

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

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

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

接下来再来使用逆变:

  1. package com.kotlin.test2
  2.  
  3. /**
  4. * 如果泛型类只是将泛型类型作为其方法的输出类型,那么我们就可以使用out
  5. *
  6. * produce = output = out
  7. */
  8. interface Producer<out T> {
  9.  
  10. fun produce(): T
  11.  
  12. }
  13.  
  14. /**
  15. * 如果泛型类只是将泛型类型作为其方法的输入类型,那么我们就可以使用in
  16. *
  17. * consumer = intput = in
  18. */
  19. interface Consumer<in T> {
  20.  
  21. fun consume(item: T)
  22.  
  23. }
  24.  
  25. /**
  26. * 如果泛型类同时将泛型类型作为其方法的输入类型与输出类型,那么我们就不能使用out与in来修饰泛型
  27. */
  28. interface ProducerConsumer<T> {
  29. fun produce(): T
  30.  
  31. fun consume(item: T)
  32. }
  33.  
  34. open class Fruit
  35.  
  36. open class Apple: Fruit()
  37.  
  38. class ApplePear: Apple()
  39.  
  40. class FruitProducer: Producer<Fruit> {
  41. override fun produce(): Fruit {
  42. println("Produce Fruit")
  43.  
  44. return Fruit()
  45. }
  46.  
  47. }
  48.  
  49. class AppleProducer: Producer<Apple> {
  50. override fun produce(): Apple {
  51. println("Produce Apple")
  52.  
  53. return Apple()
  54. }
  55.  
  56. }
  57.  
  58. class ApplePearProducer: Producer<ApplePear> {
  59. override fun produce(): ApplePear {
  60. println("Produce ApplePear")
  61.  
  62. return ApplePear()
  63. }
  64.  
  65. }
  66.  
  67. fun main(args: Array<String>) {
  68. //对于"out"泛型来说,我们可以将子类型对象赋给父类型引用
  69. var producer1: Producer<Fruit> = FruitProducer()
  70. var producer2: Producer<Fruit> = AppleProducer()
  71. var producer3: Producer<Fruit> = ApplePearProducer()
  72. }
  73.  
  74. class Human: Consumer<Fruit> {
  75. override fun consume(item: Fruit) {
  76. println("Consume Fruit")
  77. }
  78.  
  79. }
  80.  
  81. class Man: Consumer<Apple> {
  82. override fun consume(item: Apple) {
  83. println("Consume Apple")
  84. }
  85.  
  86. }
  87.  
  88. class Boy: Consumer<ApplePear> {
  89. override fun consume(item: ApplePear) {
  90. println("Consume ApplePear")
  91. }
  92.  
  93. }

接下来则来使用一下:

好,接下来理解一下:

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

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

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

从底层实现剖析Kotlin协变与逆变的原理的更多相关文章

  1. Kotlin泛型与协变及逆变原理剖析

    在上一次https://www.cnblogs.com/webor2006/p/11234941.html中学习了数据类[data class]相关的知识,这次会学习关于泛型相关的东东,其中有关于泛型 ...

  2. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  3. 在net中json序列化与反序列化 面向对象六大原则 (第一篇) 一步一步带你了解linq to Object 10分钟浅谈泛型协变与逆变

    在net中json序列化与反序列化   准备好饮料,我们一起来玩玩JSON,什么是Json:一种数据表示形式,JSON:JavaScript Object Notation对象表示法 Json语法规则 ...

  4. C#协变和逆变

    我们知道在C#中,是可以将派生类的实例赋值给基类对象的.

  5. C# 泛型的协变和逆变

    1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...

  6. 不变(Invariant), 协变(Covarinat), 逆变(Contravariant) : 一个程序猿进化的故事

    阿袁工作的第1天: 不变(Invariant), 协变(Covarinat), 逆变(Contravariant)的初次约 阿袁,早!开始工作吧. 阿袁在笔记上写下今天工作清单: 实现一个scala类 ...

  7. 再谈对协变和逆变的理解(Updated)

    去年写过一篇博客谈了下我自己对协变和逆变的理解,现在回头看发现当时还是太过“肤浅”,根本没理解.不久前还写过一篇“黑”Java泛型的博客,猛一回头又是“肤浅”,今天学习Java泛型的时候又看到了协变和 ...

  8. 【转】c# 协变和逆变

    本文转自:http://www.cnblogs.com/rr163/p/4047404.html C#的协变和逆变 由子类向父类方向转变是协变,用out关键字标识,由父类向子类方向转变是逆变,用in关 ...

  9. .NET 4.0中的泛型的协变和逆变

    转自:http://www.cnblogs.com/jingzhongliumei/archive/2012/07/02/2573149.html 先做点准备工作,定义两个类:Animal类和其子类D ...

随机推荐

  1. git删除远程.idea目录

    git删除远程.idea目录 1. 登录 Administrator@USER-20180708AB MINGW32 / (master) $ git config --global user.nam ...

  2. Springboot项目中异常拦截设计与处理

    背景: 项目运行过程中会出现各种各样的问题,常见的有以下几种情况: 业务流程分析疏漏,对业务流程的反向操作.边界分析设计不充分 调用外部服务.调用外部系统出现的超时.错误.返回值与预期不符 外部资源连 ...

  3. async + promise 解决回调地狱

    // 解决异步回调地狱的方案: async + promise async function writeFile() {   // 打开文件   const fd = await new Promis ...

  4. Java-Readable

    import java.nio.*; import java.util.*; import static net.mindview.util.Print.*; public class RandomW ...

  5. LeetCode 669. 修剪二叉搜索树(Trim a Binary Search Tree)

    669. 修剪二叉搜索树 669. Trim a Binary Search Tree 题目描述 LeetCode LeetCode669. Trim a Binary Search Tree简单 J ...

  6. 【剑指offer】面试题 6. 从尾到头打印链表

    面试题 6. 从尾到头打印链表 NowCoder 题目描述 输入一个链表的头结点,从尾到头反过来打印出每个结点的值. Java 实现 ListNode Class class ListNode { i ...

  7. 为什么Redis单线程却能支撑高并发?

    作者:Draveness 原文链接:draveness.me/redis-io-multiplexing 最近在看 UNIX 网络编程并研究了一下 Redis 的实现,感觉 Redis 的源代码十分适 ...

  8. SQL——JOIN(连接)

    JOIN基于多个表之间的共同字段,把多个表的行结合起来. 一.INNER JOIN 关键字 INNER JOIN关键字:在表中存在至少一个匹配时返回行. 语法如下: SELECT 列名1,列名2... ...

  9. Linux基础系统优化(二)

    SELinux功能 SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)对于强制访问控制的实现,这个功能管理员又爱又恨,大多数生产环境也是关闭的做法,安全手段使 ...

  10. Js学习02--变量、关键字、标识符

    一.Js变量的定义 1.定义变量的目的 在内存中分配一块存储空间给变量,方便以后存储数据. 2.如何定义变量 任何变量在使用前都必须定义变量 var 变量名称 eg: var name,age,sex ...