[版权申明] 非商业目的注明出处可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/108708218
出自:shusheng007

文章首发于个人博客

文章目录

  • 概述
    • 定义
  • Java中的情形
    • 抗变
    • 协变
    • 逆变
    • 协变与逆变的特性
  • Kotlin中的情形
  • 总结

概述

协变,逆变,抗变等概念是从数学中来的,在编程语言Java/Kotlin/C#中主要在泛型中使用。其描述的是两个类型集合之间的继承关系。有兴趣可以阅读这篇文章 An Illustrated Guide to Covariance and Contravariance in Kotlin。本文应该属于进阶知识,一般小白程序员不是没听说过就是听说过但是完全搞不明白其中的奥妙。看到即赚到,这又将是你进阶的一个台阶。

定义

首先让我们搞明白这三个名词的概念吧:

假设我们有如下两个类型集合

第一个集合为: AnimalDog , DogAnimal的子类

open class Animal
class Dog : Animal()

第二个集合为 List<Animal> List<Dog>

List<Animal>
List<Dog>

现在问题来了:由于DogAnimal的子类,那么List<Dog>就是List<Animal>的子类这句话在Kotlin/Java中对吗?

相信有一定Java/Kotlin编程经验的都可以回答的出来,答案是否定的。我们这里要说的协变,逆变,抗变就是描述上面两个类型集合的关系的。

  • 协变(Covariance):List<Dog>List<Animal>的子类型

  • 逆变(Contravariance): List<Animal>List<Dog>的子类型

  • 抗变(Invariant): List<Animal>List<Dog>没有任何继承关系

A subtype must accept at least the same range of types as its supertype declares.
A subtype must return at most the same range of types as its supertype declares.

Java中的情形

由于Kotlin是尝试对Java的改进,所以我们先来看Java的情况:

抗变

Java中泛型是抗变的,那就意味着List<String>不是List<Object>的子类型。因为如果不这样的话就会产生类型不安全问题。

例如下面代码可以通过编译的话,就会在运行时抛出异常

List<String> strs = new ArrayList<String>();
List<Object> objs = strs;
objs.add(1); // 尝试将Integer 转换为String,发生运行时异常 ClassCastException: Cannot cast Integer to String
String s = strs.get(0);

所以上面的代码在编译时就会报错,这就保证了类型安全。

但值得注意的是Java中的数组是协变的,所以数组真的会遇到上面的问题,编译可以正常通过,但会发生运行时异常,所以在Java中要优先使用泛型集合。

 String[] strs= new String[]{"ss007"};Object[] objs= strs;objs[0] = 1;

协变

抗变性会严重制约程序的灵活性,例如有如下方法copyAll,将一个String集合的内容copy到一个Object集合中,这是顺理成章的事。

// Java
void copyAll(Collection<Object> to, Collection<String> from) {to.addAll(from);
}

但是如果Collection<E>中的addAll方法签名如下的话,copyAll方法就通不过编译,因为通过上面的讲解,我们知道由于抗变性,Collection<String>不是Collection<Object>的子类,所以编译通不过。

boolean addAll(Collection<E> c);

那怎么办呢?

Java通过通配符参数(wildcard type argument)来解决, 把addAll的签名改成如下即可:

boolean addAll(Collection<? extends E> c);

? extends E 表示此方法可以接收E或者E的子类的集合。此通配符使得泛型类型协变了。

逆变

同理有时我们需要将Collection<Object>传递给Collection<String>就使用? super E,其 表示可以接收E或者E的父类,子类的位置却可以接收父类的实例,这就使得泛型类型发生了逆变

void m (List<? super String){
}

协变与逆变的特性

当使用? extends E 时,只能调用传入参数的读取方法而无法调用其修改方法。
当使用? super E时,可以调用输入参数的修改方法,但调用读取方法的话返回值类型永远是Object,几乎没有用处。

是不是感觉不好理解,确实不好理解!让我们一起看下code吧,理解了Java的这块,Kotlin的Inout关键字就手到擒来了。

例如有如下一接口,其有两个方法,一个修改,一个读取。

interface BoxJ<T> {T getAnimal();void putAnimal(T a);}

下面是两个使用通配符的方法,注意看注释

//协变,可以接受BoxJ<Dog>类型的参数private Animal getOutAnimalFromBox(BoxJ<? extends Animal> box) {Animal animal = box.getAnimal();// box.putAnimal(某个类型) 无法调用该修改方法,因为无法确定 ?究竟是一个什么类型,没办法传入return animal;}//逆变,可以接受BoxJ<Animal>类型的参数private void putAnimalInBox(BoxJ<? super Dog> box){box.putAnimal(new Dog());// 虽然可以调用读取方法,但返回的类型却是Object,因为我们只能确定 ?的最顶层基类是ObjectObject animal= box.getAnimal();}

关于Java的通配符如何使用, Effective Java, 3rd Edition 的作者将其总结为:PECS : stands for Producer-Extends, Consumer-Super. 结合上面代码分析是不是觉得很精辟。

  • Producer-Extends 只能调用读取方法,向外提供数据,无法调用修改方法
  • Consumer-Super 一般只调用修改方法,消费从外面获取的数据,调用读取方法几乎没什么用,拿到的类型永远是Object

建议自己动手尝试一下,不然还是会有点懵

那Java这种方式有没有弊端呢?Kotlin官方认为有,但是我却没怎么领会,请原谅我。其大概的意思就是说:增加了复杂性,但却没有获得相应的好处。

Kotlin中的情形

请移步到下篇… 秒懂Kotlin之彻底弄懂形变注解out与in

总结

协变,逆变和抗变,听听,你听听,是不是感觉是特别高深的概念啊,我第一次接触还是看英文文档的时候,那叫一个懵逼啊,现在看来不过如此。又应了那句老话:难者不会,会者不难。

最后记得点赞,分享加收藏

孤村落日残霞,轻烟老树寒鸦。一点飞鸿影下,青山绿水,白草红叶黄花。《 天净沙 秋》

秒懂Kotlin之协变(Covariance)逆变(Contravariance)与抗变(Invariant)相关推荐

  1. Kotlin协变和逆变

    首先声明三个类: open class Person(val name: String, val age: Int) {}class Man(val n: String, val a: Int, va ...

  2. Kotlin的型变解析(协变、逆变和不变)

    一.首先来看一个例子 import java.util.*/*** @author:wangdong* @description:型变*/fun main(args: Array<String& ...

  3. Java 泛型的协变与逆变

    协变.逆变.抗变 协变,逆变,抗变等概念是从数学中来的,在编程语言Java/Kotlin/C#中主要应用在泛型上.描述的是两个类型集合之间的继承关系. 第一个集合为:Animal.Dog , Dog ...

  4. C# 4.0中的协变和逆变(一)

    在刚刚落下帷幕的PDC上,我们得到了很多振奋的消息,包括C# 4.0及VS2010等等.Anders Liu 已经 将C# 4.0 新特性白皮书翻译了 出来,那里面有非常详细的介绍. C#的发展是很快 ...

  5. C#协变和逆变 - 译

    https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/covariance-contravariance/ ...

  6. 对协变和逆变的简单理解

    毕业快一年了,边工作边学习,虽说对.net不算精通,但也算入门了,但一直以来对协变和逆变这个概念不是太了解,上学时候mark了一些文章,今天回过头看感觉更糊涂了,真验证本人一句口头禅"知道的 ...

  7. Scala入门到精通——第二十一节 类型参数(三)-协变与逆变

    本节主要内容 协变 逆变 类型通匹符 1. 协变 协变定义形式如:trait List[+T] {} .当类型S是类型A的子类型时,则List[S]也可以认为是List[A}的子类型,即List[S] ...

  8. 泛型型协变逆变_Java泛型类型简介:协变和逆变

    泛型型协变逆变 by Fabian Terh 由Fabian Terh Java泛型类型简介:协变和逆变 (An introduction to generic types in Java: cova ...

  9. scala 协变和逆变_Scala方差:协变,不变和逆变

    scala 协变和逆变 In this post, we are going to discuss about Scala Variance and it's use cases. 在本文中,我们将讨 ...

最新文章

  1. Go开发之路 -- Go语言基本语法 - 作业
  2. qtablewidget 数据量大效率很低_让大牛带你走进大数据分析:R基础及应用的潮流尖端,享受RHadoop...
  3. 7月10日任务 添加自定义监控项目、配置邮件告警、测试告警、不发邮件的问题处理...
  4. JavaOne 2012:JavaOne技术主题演讲
  5. 栈——验证栈序列(洛谷 P4387)
  6. java获取weblogic路径_weblogic下java web项目获取根路径
  7. 线性代数中(线代中)的克莱姆法则,又译克拉默法则(Cramer‘s Rule)
  8. php 爬网页数据 入库,phpspider是一个基于QueryList3的数据PHP爬虫,页面深度爬取,超简单的使用...
  9. 爬虫抓取百度文库中的文献
  10. 中国移动NBIOT卡的几种APN应用场景
  11. 时尚html输入框,12款经典时尚的HTML5应用
  12. 拓嘉辰丰电商:一个营业执照支持开多少家店铺
  13. 六步绘制漂亮思维导图简单画法
  14. 写代码也要讲规矩——SLA
  15. uva live 4043 km
  16. linux下载安装tree命令
  17. 【C语言程序】编写登录函数,函数有两个形式函数:账号名和密码。如果账号名为“张三”,密码为“123”,则登陆成功,否则登录失败。
  18. 比较电路中的正相反馈电阻作用
  19. php 爬虫 超市,scrapy爬虫 爬取天猫进口零食网页
  20. 数据分析(1)——统计学中的各种分布

热门文章

  1. Python用数据说明程序员需要掌握的技能
  2. 产品思维训练 | 微信号终于能改了!
  3. 服务器硬盘和外储硬盘接口,服务器硬盘和普通硬盘有什么区别?
  4. python实现冒泡排序完整算法_利用python实现冒泡排序算法实例代码
  5. photoshop人物精修_如何使用Photoshop轻松删除照片中的人物
  6. Diffie-Hellman 密钥交换技术(内含多方密钥交换)
  7. 成为1个技术大牛的入门到进阶之路(学习路线图)
  8. 酒店标识软装设计之布艺装饰
  9. 可以在线ps的网站,相当于在线的photoshop
  10. Python 生成器 (通俗讲解)