协变,逆变与不变

能在使用父类型的场景中改用子类型的被称为协变。
能在使用子类型的场景中改用父类型的被称为逆变。
不能做到以上两点的被称为不变。
以上的场景通常包括数组,继承和泛型。

协变逆变与泛型(C#,Java)

在C#中,泛型参数的类型缺省是不变的,但是我们可以在定义泛型接口或委托时通过给参数类型加上out或in来标注该参数类型是协变还是逆变。

  • 协变意味着你能把 IEnumerable<string> 用在需要 IEnumerable<object> 的地方。
    这里 IEnumerable<out T> 是协变,使用 out 标注。使用 out 标注的原因是协变参数一般处于输出(output)者的地位,只读不写(read,get)。
  • 逆变意味着你能把 IComparable<object> 用在需要 IComparable<string> 的地方。
    这里 IComparable<in T> 是逆变,使用 in 标注。使用 in 标注的原因是逆变一般处于输入(input)者的地位,只写不读(write,put)。
  • Covariance and contravariance real world example

在Java中,泛型参数的类型是不变的。但是我们可以在使用泛型类或接口时在参数类型的位置上使用通配符加上extends或super来指定该参数类型是协变还是逆变。

  • List<? extends T> 是协变(参数类型只能使用T及其子类型,T是上限),用于生产者(指函数的入口参数),只读不写。
  • List<? super T> 是逆变(参数类型只能使用T及其父类型,T是下限),用于消费者(指函数的出口参数或返回值),只写不读。
  • Difference between <? super T> and <? extends T> in Java

泛型中协变逆变的类型安全性

  • 协变泛型参数是生产者,处于输出者地位,只读不写。
    协变泛型参数使用子类型代替父类型是类型安全的,这是因为在读取时子类对象可以被看做父类对象。
  • 逆变泛型参数是消费者,处于输入者地位,只写不读。
    逆变泛型参数使用父类型代替子类型是类型安全的。这是因为在写入时子类对象可以被安全的转换成父类对象。
协变泛型参数类型 逆变泛型参数类型
入口 函数 出口
输出者 输入者
子类 => 父类 ======> 子类 <= 父类
public class Collections { public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i = 0; i < src.size(); i++) dest.set(i, src.get(i)); }
}

协变与数组(C#,Java)

在C#和Java中,数组都是协变的。子类数组是父类数组的子类型,因而在调用参数是父类数组的函数时能够传入子类数组。

  • 在C#中,string[] 是 object[] 的子类型。
  • 在Java中,String[] 是 Object[] 的子类型。
  • 协变数组类型是类型不安全的,处理不当会抛出异常。
  • 协变数组类型虽然类型不安全,但是数组类型能够协变是一个合理的选择。
    这是因为早期C#和Java都缺乏泛型,如果数组不允许协变,将无法使用多态。
  • Why are arrays covariant but generics are invariant?

协变逆变与继承

C++和Java支持协变返回值类型(covariant return type),也就是允许基类虚函数(方法)在子类中被覆盖(重写)时,子类中相应虚函数(方法)的返回值类型不同于基类虚函数(方法)的返回值类型,条件是前者是后者的子类型。C#不支持这一特性。

class VehicleFactory {public:virtual Vehicle * create() const { return new Vehicle(); }virtual ~VehicleFactory() {}
};class CarFactory : public VehicleFactory {
public:virtual Car * create() const override { return new Car(); } // 协变返回值类型// 子类虚函数create的返回值类型Car *是基类虚函数create的返回值类型Vehicle *的子类型。
};

某些语言支持逆变(协变)参数类型(contravariant/covariant argument type),也就是允许基类虚函数(方法)在子类中被覆盖(重写)时,子类中相应虚函数(方法)的参数类型不同于基类虚函数(方法)的参数类型,条件是前者是后者的父(子)类型。C++,C#以及Java均不支持逆变或协变参数类型。

继承中协变逆变的类型安全性

  • 在子类中使用子类型返回值类型代替父类中的父类型返回值类型(协变返回值类型)是类型安全的。
  • 在子类中使用父类型参数类型代替父类中的子类型参数类型(逆变参数类型)是类型安全的。
  • 在子类中使用子类型参数类型代替父类中的父类型参数类型(协变参数类型)是类型不安全的。
逆变参数类型 协变返回值类型
参数 函数 返回值
父类 => 子类 ======> 父类 <= 子类

协变逆变,父子类型关系以及类型转换(转型)

协变意味着能在使用父类型的场景中改用子类型,也就是在新场景中使用子类型后仍然得到子类型。
逆变意味着能在使用子类型的场景中改用父类型,也就是在新场景中使用父类型后却能得到子类型。
下面改用符号语言:
S是T的子类型(S能够被安全的转型成T),记作 S <: T。
协变:在新场景 F 中,父子类型的关系被维持,即 F(S) <: F(T)。
逆变:在新场景 F 中,父子类型的关系被颠覆,即 F(T) <: F(S)。
不变:在新场景 F 中,父子类型的关系不明。

协变逆变与函数类型

函数 f 的参数类型为A,返回值类型为B,记作 A -> B。
协变:B <: B’ => A -> B <: A -> B’(协变返回值类型)
逆变:A <: A’ => A’ -> B <: A -> B(逆变参数类型)
函数型语言中函数类型通常满足协变返回值类型和逆变参数类型。
Can parameters be contra- or covariant in Python?

协变逆变与C++

在C++中,指针和引用都是协变的,也就是在使用父类指针和引用的场景中能够安全的使用子类指针和引用。
这是因为子类指针和引用是父类指针和引用的子类型,语法上前者能够被安全地隐式地转换成后者,

在C++中,模板的参数类型是不变的。但是标准库的某些类型支持协变和逆变。

  • shared_ptr类型支持协变,也就是shared_ptr<子类>是shared_ptr<父类>的子类型。
  • unique_ptr类型支持协变,也就是unique_ptr<子类>是unique_ptr<父类>的子类型。
  • 以上两者是协变的原因是以上两者都是智能指针,需要模仿原生指针的特性。
  • function的类型参数中的返回值类型支持协变,也就是function<子类*(T)>是function<父类*(T)>的子类型。
  • function的类型参数中的参数类型支持逆变,也就是function<U(父类*)>是function<U(子类*)>的子类型。
#include <iostream>
#include <memory>
#include <functional>
using namespace std;struct Base {};
struct Derived : Base {};int main()
{shared_ptr<Derived> p = nullptr;shared_ptr<Base> p2 = p; // shared_ptr类型支持协变function<Derived*(Base*)> f = nullptr;function<Base*(Derived*)> f2 = f; // function的函数参数类型支持逆变,function的函数返回值类型支持协变
}

Covariance and Contravariance in C++ Standard Library

协变(covariance),逆变(contravariance)与不变(invariance)相关推荐

  1. 秒懂Kotlin之协变(Covariance)逆变(Contravariance)与抗变(Invariant)

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

  2. Scala中协变(+)、逆变(-)、上界(:)、下界(:)简单介绍

    对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么就称为covariance(协变) , 如果 List[A]是 List[B ...

  3. typescript中的协变、逆变

    typescript中的协变.逆变 ​ 想要写出更加优秀的类型编程co-variance(协变)contra-variance(逆变)这类知识是我们必须掌握的.这篇记录也仅仅是为了方便之后哪天这块知识 ...

  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. 在本文中,我们将讨 ...

  10. TypeScript 协变和逆变

    TypeScript 协变和逆变 文章目录 TypeScript 协变和逆变 前言 内涵和外延 LSP(里氏替换原则) 定义 理解 第一层 第二层 第三层 技巧 参考 前言 内涵和外延 说协变和逆变前 ...

最新文章

  1. R语言stringr包str_detect函数检测字符串中模式存在与否实战
  2. 获得H.264视频分辨率的方法
  3. 第一个通过HCIEv3.0的咱的学员
  4. 全球及中国香蕉连接器行业投资商机与前景趋势展望报告2022版
  5. python将列表写入csv_转:Python 将列表数据写入文件(txt, csv, excel)
  6. java digests.generatesalt_Java DigestUtils.sha1Hex方法代碼示例
  7. Selenium WebDriver Api 知识梳理
  8. [转]研究生阶段学习规划指导随笔
  9. egg mysql insert_egg-mysql
  10. oracle的jde系统,国内Oracle JDE用户的福音,首创AWS JDE Dynamic Adapter集成中间件
  11. 微信小程序的测试方案总结
  12. Python构建投资模型(1)——从天天基金网爬数
  13. mac android使用WiFi安装应用调试程序
  14. vsftpd虚拟账户(虚拟用户,ubuntu16,舍弃虚拟用户配置文件)
  15. Linux系统安装教程(非双系统/虚拟机安装教程)
  16. ANSYS_APDL在绘制 vonMises(等效)应力云图报错:The requested data S is not available.The PLES command is ignored.
  17. [Perl]如何取绝对路径:FindBin模块和Cwd模块用法和区别
  18. HTTP权威指南读后感
  19. 二级域名配置以及nginx解析二级域名到html页面
  20. 微机原理与接口技术[第三版]——第五章课后习题答案

热门文章

  1. 蒲慕明:《大脑之美》序言,脑探索的起点
  2. 上海积分落户计算机水平加分,2019年上海应届生落户打分72分标准拿分策略
  3. classid 是什么意思?
  4. Codefi基于区块链的开发框架
  5. python在线解题_1000题库系列一
  6. 苏杰的产品创新课/图书/企业服务,双11价格确实便宜
  7. 学生管理系统实现增删改查
  8. 阿里CEO逍遥子:学会“用人做事”,而不是“做事用人”
  9. GeoGebra介绍
  10. 5565系列反射内存