[版权申明] 非商业目的注明出处可自由转载
博文地址: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. 连接统计学、机器学习与自动推理的新兴交叉领域——因果科学读书会再起航...
  2. SAP PM 后台配置TCODE
  3. 90年代谁最强?乔丹不可撼动石佛上榜
  4. 【Tools】Visual Studio 2010下载和安装
  5. 使用PuTTY、Xshell远程连接Linux,密钥认证连接
  6. Volley 源码解析之网络请求
  7. opencv空间色彩转换
  8. 2019年网络规划设计师上午真题及答案解析
  9. 不使用第三个变量的情况下,实现两个变量间的互换。
  10. js 求数组中最小值
  11. 煤矿调度计算机使用管理制度,煤矿调度文档管理制度(参考).doc
  12. 运放专题:电压电流转换
  13. Oracle 18c十大新特性
  14. HWDB1.1数据集 | 手写汉字数据集 |.gnt 转换 .png格式图片| 【❤️有效转换❤️】
  15. 程序员干累了,当个培训讲师?我亲身试水,讲讲感受
  16. 基于Kappa-mu/M分布的联合多用户分集与并行中继继选择RF/FSO系统性能研究
  17. 《无尽的拉格朗日》--Day10体验
  18. 实战|QUIC协议在蚂蚁集团落地
  19. 数据库查询和数据操纵
  20. UESTC人工智能 期末复习

热门文章

  1. auto CAD里用Adobe PDF虚拟打印机遇到的若干问题
  2. 全球首发搭载国产北斗芯片,联想Z6青春版千元强悍出击
  3. 相似图片去重--余弦相似度和sift算法
  4. show sga解析
  5. 好用的数据校验修复工具gt-checksum开源啦
  6. 六一儿童节陪孩子 游玩景区大全
  7. Xray和AWVS联动实现主动扫描
  8. 网络营销(直播营销)
  9. 日媒 Jungle City 专访:打造让区块链技术易于使用的开发者平台 | ArcBlock 媒体
  10. ccxprocess启动项可以禁用么_Mac使用技巧:提高系统运行速度 可以禁止Adobe自启动...