这一次,终于弄懂了协变和逆变

一、前言

刘大胖决定向他的师傅灯笼法师请教什么是协变和逆变。

 

刘大胖:师傅,最近我在学习泛型接口的时候看到了协变和逆变,翻了很多资料,可还是不能完全弄懂。

灯笼法师:阿胖,你不要被这些概念弄混,编译器可不知道你说的什么协变逆变。这个问题,首先你得弄懂什么叫类型的可变性。

刘大胖:可变性?

二、可变性

灯笼法师:对,可变性是以一种类型安全的方式,将一个对象作为另一对象来引用。虽然是可变,但其实对象的引用地址是不会变的,只是忽悠下编译器。

刘大胖:师傅说的将一个对象作为另一对象来引用?这不就是继承么?

灯笼法师:是的,你可以看下面代码演示(C#):

刘大胖:哦,我理解了,由于MemoryStream继承于Stream,所以MemoryStream的对象可以变为Stream的对象,原来我天天在接触可变性,我竟然不知道。

灯笼法师:是的,这种转变其实遵守了里氏替换原则,爱徒,你可还记得?

刘大胖:当然,为了面试早已烂熟于心。里氏替换原则(LSP):指的是所有引用基类的地方都可以使用其子类的对象。可是师傅,这个和协变逆变有什么关系呢?

三、协变

灯笼法师:协变和逆变只是可变性的分类,主要用于泛型接口和委托中。协变逆变只是类型转换的方向不同。我们先看下接口协变吧,假如有Apple类继承于Fruit,如下:

灯笼法师:然后现在写了一个打印水果名称的方法,如下:

灯笼法师:这时如果你打算打印一些苹果的名称,你会怎么写?

刘大胖:这不是很简单,Apple继承自Fruit,那可以直接使用PrintFruit类了。撸了下,怎么报错了?代码如下:

灯笼法师:大胖,你要理清楚,虽然Apple继承Fruit,但List<Apple>和List<Fruit>却一点关系也没有,如图:

刘大胖:那如果这样,岂不是要为每一种水果都要定义一个PrintFruit方法,我觉得官方不会不知道这个问题吧?

灯笼法师:这种问题,官方当然知道了,所以才有了泛型接口的协变用以支持List<Apple>自动转为List<Fruit>。C#中使用out表示泛型参数的可协变性,List没有out约束,所以不能协变,但它的基类IEnumable却实现了,如图:

灯笼法师:所以只要把PrintFruit的参数类型换成IEnumable就可以了,如图:

刘大胖:那为什么List<T>不能加out以支持协变呢?

灯笼法师:爱徒问的好,List继承于IEnumable,它比IEnumable更宽泛,它支持读和写,但协变只能可读,主要用于约束输出参数。

刘大胖:好吧,我回去再消化下。师傅你再讲一下什么是逆变吧。

四、逆变

灯笼法师:逆变是相反的,即支持List<Fruit>转为List<Apple>,泛型接口上添加in约束输入参数。

刘大胖:有点懞,师傅你还是用代码吧!

灯笼法师:好吧,假如现在我要让苹果列表或桔子列表可以按名称排序,需要一个定义一个水果比较器,此比较器能用于任何种类的水果列表,代码如下:

灯笼法师:现在给苹果和桔子列表按名称排序吧,代码如下:

刘大胖:师傅你别忽悠我,Sort的参数可是要具体类型的比较器的,你看代码:

灯笼法师:大胖,就这是逆变,以使得基类的泛型对象替代子类的泛型对象,主要是因为IComparer<T>中使用了in关键字来约束,代码如下:

五、总结

刘大胖:哦,我有点明白了,协变就是支持泛型子类自动转泛型父类,逆变就是支持泛型父类自动转泛型子类。

灯笼法师:也可以这么理解,但这些转换只是针对编译器,其引用地址并没有改变。

翻外篇1:

协变:String =>Object

逆变:Object => String

翻外篇2:

灯笼法师在刘大胖走后从背后拿出手机,屏幕上显示来不及关闭的知乎APP:

更多精彩文章,可以关注我的公众号:

原文地址:https://www.cnblogs.com/liuzhenbao/p/12638103.html

时间: 2024-11-05 15:21:52

这一次,终于弄懂了协变和逆变的相关文章

C# - 协变、逆变 看完这篇就懂了

1. 基本概念 官方:协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型.[MSDN] 公式:           协变:IFoo<父类> = IFoo<子类>:           逆变:IBar<子类> =  IBar<父类>: 暂时不理解没关系,您接着往下看. 2. 协变(Covariance) 1) out关键字 对于泛型类型参数,out 关键字

泛型中协变和逆变

写在前面 今天讲的内容有点多,但是差不多都能听懂,稍微有点模糊的就是协变和逆变的概念,下面是我结合在网上看的资料整合而成的. 正文 msdn上的原话: 协变:是指能够使用比原始指定的派生类型的派生程度更小(不太确定)的类型 逆变:是指能够使用比原始类型的派生类型的派生程度更大(更具体)的类型 在方便理解的概念是: 协变:子类向父类转化,用于返回类型用out关键字 逆变:父类向子类转化的过程,用于方法参数类型用in关键字 协变的例子: 1 public class Person { } 2 3 p

厘清泛型参数的协变与逆变

协变与逆变(CoVariant and ContraVariant),很多人是糊涂的,我也一直糊涂.其实,对协变与逆变概念糊涂,甚至完全不知道,对一般程序员也没有很大影响.不过,如果你想提高水平,想大概看懂.Net Framework类库中那些泛型接口与泛型类,想大概弄清楚Linq,这个概念还是需要搞清楚. 话又说回来,想弄清楚,其实还是挺费劲的. 如果你还糊涂着这两个概念,相信我,认真看完下面的文字,你会对泛型参数的协变与逆变有一个清晰的理解. 想透彻掌握协变.逆变的概念,首先需要对接口.委托

协变、逆变与不变:数组、泛型、与返回类型

转自:http://blog.csdn.net/yi_Afly/article/details/52071260 1. 前言 之前几篇博文,有些地方涉及到了协变性.逆变性与不变性在Java中的表现,所以这篇博文将重点记录这方面的内容,并辅以JDK源码中的一些实例,加以说明. 2. 定义 这里讨论的协变.逆变与不变都是编程语言中的概念.下面介绍定义: 若类A是类B的子类,则记作A ≦ B.设有变换f(),若: 当A ≦ B时,有f(A)≦ f(B),则称变换f()具有协变性. 当A ≦ B时,有f

那些年搞不懂的&quot;协变&quot;和&quot;逆变&quot;

博主之前也不是很清楚协变与逆变,今天在书上看到了有关于协变还是逆变的介绍感觉还是不太懂,后来看了一篇园子里面一位朋友的文章,顿时茅塞顿开.本文里面会有自己的一些见解也会引用博友的一些正文,希望通过本篇,能让大家对协变与逆变不再陌生. What's 协变逆变? 从字面理解协变就是"妥协的变化",而逆变则是"逆天的变化",哈哈,并不标准,我们来看看MSDN的解释: “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型. “逆变”则是指能够使用派生程度更小的类

Java进阶知识点2:看不懂的代码 - 协变与逆变

要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那么我们可以在任何时候使用MochaCoffee类的引用去替换Coffee类的引用(重写函数时,形参必须与重写函数完全一致,这是一处列外),而不会引发编译错误(至于会不会引发程序功能错误,取决于代码是否符合里氏替换原则). 简而言之,如果B类是A类的派生类,那么B类的引用可以赋值给A类的引用. 赋值的方式最常见

.NET可变性解析(协变和逆变)

[一]何为可变性 可变性是.NET4.0中的一个新特性,可变性可分为 : 协变性.逆变性.不可变性. 那么在.NET4.0之前是否有可变性? 答案是肯定的,我们可以通过下面的几个实例来简单的了解一下.NET4.0之前的协变和逆变. 实例 1 : 方法参数的协变 static void Main(string[] args) { GetProject(new Course()); // Course 继承自 Project 此处进行了协变 } static void GetProject(Proj

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

作者:摇摆少年梦 视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 协变 逆变 类型通匹符 1. 协变 协变定义形式如:trait List[+T] {} .当类型S是类型A的子类型时,则List[S]也可以认为是List[A}的子类型,即List[S]可以泛化为List[A].也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变(covariance). 图1 协变示意图 为方便大家理解,我们先分析java语言中为什么不存在协变及下一

Scala中的类、接口及协变和逆变

 4.   OOP 4.1.     类class 4.1.1.  定义 例子1: class User { var name = "anonymous" var age:Int = _ val country = "china" def email = name + "@mail" } 使用: val u = new User // var定义的属性可读可写 u.name = "qh"; u.age = 30 print