Java作为面向对象的典型语言,相比于C++而言,对类的继承和派生有着更简洁的设计(比如单根继承)。

  在继承派生的过程中,是符合Liskov替换原则(LSP)的。LSP总结起来,就一句话:

    所有引用基类(父类)的地方必须能够透明地使用其子类的对象。

  LSP包含四层含义:

    ① 子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法;

    ② 子类中可以增加自己的方法;

    ③ 当子类覆盖或实现父类的方法时,方法的形参要比父类方法的更加宽松;

    ④ 当子类覆盖或实现父类的方法时,方法的返回值要比父类方法的更加严格。

  针对LSP四层含义的③④条,就引出了协变(Covariance)和逆变(Contravariance)的概念:

    协变,简言之,就是父类型到子类型,变得越来越具体,在Java中体现在返回值类型不变或更加具体(异常类型也是如此)等。

    逆变,简言之,就是父类型到子类型,变得越来越具体,但是方法的形参却变得更加抽象或不变(注意:这里在Java中本质为方法重载,而不是覆盖,当添加@Override标签将会报错!)

  于是,针对上述的逆变,概念可以理解,但是Java中所谓的“方法形参变得更加宽泛”,实质是方法重载,似乎也就不能严格地称为继承关系下的“逆变”,思考之,似乎Java的继承派生过程中,几乎所有的操作,都是协变的,那么是不是就说明Java中并不存在逆变呢???

  错!Java中是存在逆变的!

  这里就引出了Java中的泛型,这里举个例子:

  在Java中,Number类是Integer类的父类(super),如果某个方法的签名是void method(List<Number> listNumber),那么按照协变的思想,是不是意味着为这个方法传入List<Integer>类型参数也是可以的呢?

  当然不... 很多博客在此都说“Java对于这样的泛型是不支持协变的”,但我认为,事实是List<Integer>的实参,依旧是一个List类型的持有对象,因此对于List<Number>这个持有对象来说,二者持有的对象存在继承派生的关系,但二者本身并不存在继承派生的关系,因而也就无从谈及协变(实质,这里二者的关系是“不变”,“不变”是针对于协变与逆变概念而言的)。

  举个例子来说明这个简单的问题,一个父亲和他的儿子都分别有一辆车,他们的车款型相同(当然,也可能不同,但总归是车,即持有对象,这里为了针对上述二者持有对象均为List故意言之),尽管车上的父亲和儿子存在着“继承派生”的关系,但是这两辆车并不存在继承关系,所以二者之间并没有“协变”的概念。

  那么,总不能对于这样的method,要为每种持有对象持有的对象类型分别重载实现method吧...于是,Java就提供了泛型的通配符(注意,这里才谈到泛型),为了解决上述的method问题,可以这样声明method: void method(List<? extends Number> listNumber)。这样,这里的形参就必须是一个持有对象,它持有的对象类型,必须是Number类或者是继承自Number类的更具体的子类(如Integer类,Double类),此时,可以说这个方法依旧实现了“协变”,那么Java中的逆变是体现在哪儿的呢?

  这里就引出了通配符后另一个关键字,super。

  这样声明的方法:void method(List<? super Integer> listInteger),说明该持有对象持有的对象类型,必须是Integer或Integer的父类(超类super),于是,此时向方法中传递持有Number类的持有对象也是可以的,甚至,可以传递一个持有Object类型的持有对象。此处便是使得参数类型变得更加宽泛,因此此处体现的是“逆变”。

  这也很好记:

    ? extends 对应 协变

    ? super    对应 逆变

    (? 即为Java泛型的通配符)

  综上,Java是符合LSP的一门语言,对“协变”“逆变”的支持也是有具体实现以及道理的。理解好这些概念,可以让编程中遇到的知识概念更加系统化,理解记忆也更高效。

  至于前几天,有同学在群里讨论,Java中如果子类覆盖了父类的方法,是否就不符合LSP了,如果说Java是严格按照LSP来设计的,那么这种情况是否就不能称为覆盖,而是重载...

  当时被这个问题雷到了... 我理解的LSP应该是一种思想,是设计过程以及实现过程中开发者应该牢记并遵守的。如果按照LSP的总的规则,那么每个父类对象出现的地方,都可以用其具体子类对象来替换而不会发生错误。这个错误当然是保证语法编译不会发生错误,而不是针对覆盖方法导致的功能不同。所以...这个问题,实质上应该归结于理解发生了偏颇...

  本博客参考博客:

  Java协变和逆变:https://blog.csdn.net/qiuchengjia/article/details/52910901

  Java的逆变与协变:https://www.cnblogs.com/en-heng/p/5041124.html

  from Steven Shen

    编辑于2018.6.22

    修改于2019.9.4

  

Java中的协变与逆变的更多相关文章

  1. Scala中的协变,逆变,上界,下界等

    Scala中的协变,逆变,上界,下界等 目录 [−] Java中的协变和逆变 Scala的协变 Scala的逆变 下界lower bounds 上界upper bounds 综合协变,逆变,上界,下界 ...

  2. Java泛型中的协变和逆变

    Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛 ...

  3. .net中的协变和逆变

    百度:委托中的协变和逆变. 百度:.net中的协变和逆变. 协变是从子类转为父类. 逆变是从父类到子类. 这样理解不一定严谨或者正确.需要具体看代码研究.

  4. C#4.0中的协变和逆变

    原文地址 谈谈.Net中的协变和逆变 关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Anima ...

  5. Java语言中的协变和逆变(zz)

    转载声明: 本文转载至:http://swiftlet.net/archives/1950 协变和逆变指的是宽类型和窄类型在某种情况下的替换或交换的特性.简单的说,协变就是用一个窄类型替代宽类型,而逆 ...

  6. Java泛型的协变与逆变

    泛型擦除 Java的泛型本质上不是真正的泛型,而是利用了类型擦除(type erasure),比如下面的代码就会出现错误: 报的错误是:both methods  have same erasure ...

  7. C#4.0新增功能03 泛型中的协变和逆变

    连载目录    [已更新最新开发文章,点击查看详细] 协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体 ...

  8. C# 中的协变和逆变

    作为一个从接触 Unity 3D 才开始学习 C# 的人,我一直只了解一些最基本.最简单的语言特性.最近看了<C# in Depth>这本书,发现这里面东西还真不少,即使除去和 Windo ...

  9. .NET泛型中的协变与逆变

    泛型的可变性:协变性和逆变性 实质上,可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用. 我们已经习惯了普通继承中的可变性:例如,若某方法声明返回类型为Stream,在实现时可以返回一个M ...

随机推荐

  1. 基于Tesseract实现图片文字识别

    一.简介  Tesseract是一个开源的文本识别[OCR]引擎,可通过Apache 2.0许可获得.它可以直接使用,或者使用API从图像中提取打印的文本,支持多种语言.该软件包包含一个ORC引擎[l ...

  2. Spark Streaming实践和优化

    发表于:<程序员>杂志2016年2月刊.链接:http://geek.csdn.net/news/detail/54500 作者:徐鑫,董西成 在流式计算领域,Spark Streamin ...

  3. 世界协调时间(UTC)与中国标准时间

    整个地球分为二十四时区,每个时区都有自己的本地时间.在国际无线电通信场合,为了统一起见,使用一个统一的时间,称为通用协调时(UTC, Universal Time Coordinated).UTC与格 ...

  4. 运行时Runtime的API

    const char * class_getName(Class cls); 返回类的名称. Class class_getSuperclass(Class cls); 返回类的超类. Class c ...

  5. springboot2.x整合redis

    pom文件 <!--springboot中的redis依赖--> <dependency> <groupId>org.springframework.boot< ...

  6. python3下pygame显示中文的设置

    1.先看代码: import pygame from pygame.locals import * def main(): pygame.init() screen = pygame.display. ...

  7. Edge Beta Android版更新已启用新图标

    导读 微软Edge Beta Android版更新已启用新图标设计 IT之家消息 适用于Android的Microsoft Edge Beta已于近日获得更新,最显著的特征就是使用了新图标设计.该图标 ...

  8. python中解方程

    from sympy import * import numpy as np from numpy import linalg # 方程中的符号 x = Symbol('x') # 计算 result ...

  9. day04-MyBatis的缓存与懒加载

    为什么会用到缓存? 为了减少与数据库链接所消耗的时间,将查询到的内容放到内存中去,下次查询直接取用就ok了. 缓存的适应场景: 1.经常查询并且不经常改变的. 2.数据的正确与否对最终结果影响不大的. ...

  10. 阿里云短信接口开发实践(Java

    随着互联网的兴起,各行各业的需求都在不断的增加.随着业务的扩大,企业给用户发送短信验证码的业务,也是如火如荼.在这里,calvin给各位开发者推荐阿里云短信平台.原因有二:1.接入较简单,开发成本低 ...