从底层实现剖析Kotlin协变与逆变的原理
继续还是探究协变与逆变,在正式开始之前,先来对Kotlin和Java的协变与逆变进行一个对比:
1、Kotlin是声明处协变;而在Java中是在使用处协变:
如何理解,我们先来回顾一下在Java使用协变的写法:

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

2、Kotlin中的out关键字叫做variance annotation,因为它是在类型参数声明处所指定的,因此我们称之为声明处协变(declaration-site variance);而对Java来说则是使用处协变(use-site variance),其中类型通配符使得类型协变成为可能。
另外需要注意:对于Java的这个泛型声明不要跟协变搞混了,如下:

它跟Java的使用处协变是完全不同的概念,使用协变一定是<? extends xxxx>。
好,接一来再来以一个完整的例子进一步巩固Kotlin的协变、逆变、不变的知识点,如下:

接下来定义逆变:

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

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

如果是声明成协变,则只能读,如果声明成逆变,则只能写。
好,继续,接下来再定义三个类:

接下来则定义协变类:
package com.kotlin.test2 /**
* 如果泛型类只是将泛型类型作为其方法的输出类型,那么我们就可以使用out
*
* produce = output = out
*/
interface Producer<out T> { fun produce(): T } /**
* 如果泛型类只是将泛型类型作为其方法的输入类型,那么我们就可以使用in
*
* consumer = intput = in
*/
interface Consumer<in T> { fun consumer(item: T) } /**
* 如果泛型类同时将泛型类型作为其方法的输入类型与输出类型,那么我们就不能使用out与in来修饰泛型
*/
interface ProducerConsumer<T> {
fun produce(): T fun consumer(item: T)
} open class Fruit open class Apple: Fruit() class ApplePear: Apple() class FruitProducer: Producer<Fruit> {
override fun produce(): Fruit {
println("Produce Fruit") return Fruit()
} } class AppleProducer: Producer<Apple> {
override fun produce(): Apple {
println("Produce Apple") return Apple()
} } class ApplePearProducer: Producer<ApplePear> {
override fun produce(): ApplePear {
println("Produce ApplePear") return ApplePear()
} }
下面来使用一下:

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

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

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

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

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

接下来再来使用逆变:
package com.kotlin.test2 /**
* 如果泛型类只是将泛型类型作为其方法的输出类型,那么我们就可以使用out
*
* produce = output = out
*/
interface Producer<out T> { fun produce(): T } /**
* 如果泛型类只是将泛型类型作为其方法的输入类型,那么我们就可以使用in
*
* consumer = intput = in
*/
interface Consumer<in T> { fun consume(item: T) } /**
* 如果泛型类同时将泛型类型作为其方法的输入类型与输出类型,那么我们就不能使用out与in来修饰泛型
*/
interface ProducerConsumer<T> {
fun produce(): T fun consume(item: T)
} open class Fruit open class Apple: Fruit() class ApplePear: Apple() class FruitProducer: Producer<Fruit> {
override fun produce(): Fruit {
println("Produce Fruit") return Fruit()
} } class AppleProducer: Producer<Apple> {
override fun produce(): Apple {
println("Produce Apple") return Apple()
} } class ApplePearProducer: Producer<ApplePear> {
override fun produce(): ApplePear {
println("Produce ApplePear") return ApplePear()
} } fun main(args: Array<String>) {
//对于"out"泛型来说,我们可以将子类型对象赋给父类型引用
var producer1: Producer<Fruit> = FruitProducer()
var producer2: Producer<Fruit> = AppleProducer()
var producer3: Producer<Fruit> = ApplePearProducer()
} class Human: Consumer<Fruit> {
override fun consume(item: Fruit) {
println("Consume Fruit")
} } class Man: Consumer<Apple> {
override fun consume(item: Apple) {
println("Consume Apple")
} } class Boy: Consumer<ApplePear> {
override fun consume(item: ApplePear) {
println("Consume ApplePear")
} }
接下来则来使用一下:

好,接下来理解一下:

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

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

这不还是多态的应用么,一个子类对象赋值给父类对象嘛,所以,协变和逆变的根源其实就是多态在起着关键作用。所以至此咱们对于协变与逆变的了解又更加深入了,这个概念是非常之重要的,了解透了之后对于学习像scala这样的语言关于这些概念就很轻松了。
从底层实现剖析Kotlin协变与逆变的原理的更多相关文章
- Kotlin泛型与协变及逆变原理剖析
在上一次https://www.cnblogs.com/webor2006/p/11234941.html中学习了数据类[data class]相关的知识,这次会学习关于泛型相关的东东,其中有关于泛型 ...
- C#4.0泛型的协变,逆变深入剖析
C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...
- 在net中json序列化与反序列化 面向对象六大原则 (第一篇) 一步一步带你了解linq to Object 10分钟浅谈泛型协变与逆变
在net中json序列化与反序列化 准备好饮料,我们一起来玩玩JSON,什么是Json:一种数据表示形式,JSON:JavaScript Object Notation对象表示法 Json语法规则 ...
- C#协变和逆变
我们知道在C#中,是可以将派生类的实例赋值给基类对象的.
- C# 泛型的协变和逆变
1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...
- 不变(Invariant), 协变(Covarinat), 逆变(Contravariant) : 一个程序猿进化的故事
阿袁工作的第1天: 不变(Invariant), 协变(Covarinat), 逆变(Contravariant)的初次约 阿袁,早!开始工作吧. 阿袁在笔记上写下今天工作清单: 实现一个scala类 ...
- 再谈对协变和逆变的理解(Updated)
去年写过一篇博客谈了下我自己对协变和逆变的理解,现在回头看发现当时还是太过“肤浅”,根本没理解.不久前还写过一篇“黑”Java泛型的博客,猛一回头又是“肤浅”,今天学习Java泛型的时候又看到了协变和 ...
- 【转】c# 协变和逆变
本文转自:http://www.cnblogs.com/rr163/p/4047404.html C#的协变和逆变 由子类向父类方向转变是协变,用out关键字标识,由父类向子类方向转变是逆变,用in关 ...
- .NET 4.0中的泛型的协变和逆变
转自:http://www.cnblogs.com/jingzhongliumei/archive/2012/07/02/2573149.html 先做点准备工作,定义两个类:Animal类和其子类D ...
随机推荐
- [LeetCode] 160. Intersection of Two Linked Lists 求两个链表的交集
Write a program to find the node at which the intersection of two singly linked lists begins. For ex ...
- 机器学习技法总结(四)(aggregation,vote,bootstrap...)
研究的动机是:我们采用了不同的模型得到T个不同的g,那么我们是不是可以通过这些不同的g的融合得到更加出色的G呢?因此,便有了以上四种不同的方法:1)(select)直接选择最好的一个作为融合的结果:2 ...
- String字符串相关操作
.length 字符串长度.equals 比较字符串.equalIgnoreCase 比较字符串不区别大小写.charAt 获取字符串指定下标位置的字符.contains 判断字符串内是否包含某字符串 ...
- 腾讯的网站如何检测到你的 QQ 已经登录?
转:http://www.lovelucy.info/tencent-sso.html 在 QQ 已经登录的情况下,手动输入网址打开 QQ 邮箱 或者 QQ 空间 等腾讯网站,可以看到网页已经检测到本 ...
- LeetCode 86. 分隔链表(Partition List)
86. 分隔链表 86. Partition List 题目描述 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前. 你应当保留两个分区中每个节点的 ...
- spring框架学习(三)——AOP( 面向切面编程)
AOP 即 Aspect Oriented Program 面向切面编程 首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能. 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务 ...
- STL源码剖析——序列式容器#1 Vector
在学完了Allocator.Iterator和Traits编程之后,我们终于可以进入STL的容器内部一探究竟了.STL的容器分为序列式容器和关联式容器,何为序列式容器呢?就是容器内的元素是可序的,但未 ...
- Vue框架基础02
摘要 条件指令 循环指令 评论案例 实例成员之computed 实例成员之watch 分隔符 组件:局部组件和全局组件 局部组件与全局组件之间信息传输 一.条件指令扩展 <!DOCTYPE ht ...
- 15 IO流(十二)——数据流Data InputStream/OutputStream 未学会
数据流的引入 Data流的父类是Filter抽象基类,也就是说Data流是装饰流. 数据流可以将数据的类型也一起传输. 数据流的读取写入顺序(数据类型的读写顺序)需要一致. 未完成代码 /** *Da ...
- gorm 批量插入数据
使用gorm 插入数据的时候,根据官方文档可以使用Create或者FirstOrCreate(). 但是官方没有提供批量插入数据的方法. 根据github的 issue得知,我们可以通过自己拼接sql ...