本文整理自:https://chiclaim.blog.csdn.net/article/details/85575213

我们先定义三个类:Plate、Food、Fruit

//定义一个`盘子`类
public class Plate<T> {private T item;public Plate(T t) {item = t;}public void set(T t) {item = t;}public T get() {return item;}}//食物
public class Food {}//水果类
public class Fruit extends Food {
}

然后定义一个takeFruit()方法

private static void takeFruit(Plate<Fruit> plate) {
}

1、不变性 (invariance)

调用takeFruit方法,尝试把一个装着苹果的盘子传进去:

takeFruit(new Plate<Apple>(new Apple())); //泛型之不变

发现编译器报错,发现装着苹果的盘子竟然不能赋值给装着水果的盘子,这就是泛型的不变性 (invariance)

这个时候就要引出泛型的协变性

2、协变性

假设我就要把一个装着苹果的盘子赋值给一个装着水果的盘子呢?

我们来修改下 takeFruit 方法的参数 (? extends Fruit):

private static void takeFruit(Plate<? extends Fruit> plate) {
}

然后调用 takeFruit 方法,把一个装着苹果的盘子传进去:

takeFruit(new Plate<Apple>(new Apple())); //泛型的协变

这个时候编译器不报错了,而且你不仅可以把装着苹果的盘子放进去,还可以把任何继承了 Fruit 类的水果都能放进去:

//包括自己本身 Fruit 也可以放进去
takeFruit(new Plate<Fruit>(new Fruit()));
takeFruit(new Plate<Apple>(new Apple()));
takeFruit(new Plate<Pear>(new Pear()));
takeFruit(new Plate<Banana>(new Banana()));

在 Java 中把 ? extends Type 类似这样的泛型,称之为 上界通配符(Upper Bounds Wildcards)

为什么叫上界通配符?因为 Plate<? extends Fruit>,可以存放 Fruit 和它的子类们,最高到 Fruit 类为止。所以叫上界通配符

好,现在编译器不报错了,我们来看下 takeFruit 方法体里的一些细节:

private static void takeFruit(Plate<? extends Fruit> plate) {//plate5.set(new Fruit());    //编译报错//plate5.set(new Apple());    //编译报错Fruit fruit = plate5.get();   //编译正常
}

发现 takeFruit() 的参数 plate 的 set 方法不能使用了,只有 get 方法可以使用。

为什么参数 plate 的 set 方法不能使用了?因为Plate<? extends Fruit> plate这里说明了plate中适配的是Fruit类和它的子类们;而即使我们通过set 方法设置的也是Fruit的子类,但是其实并不能保证他们之间是兼容的。

如果我们需要调用 set 方法呢?这个时候就需要引入泛型的逆变性

3、逆变性

修改下泛型的形式 (extends 改成 super):

private static void takeFruit(Plate<? super Fruit> plate){plate.set(new Apple());     //编译正常//Fruit fruit = plate.get(); //编译报错//Fruit pear = plate.get();   //编译报错
}

发现 set 方法可以用了,但是 get 方法“失效”了。我们把类似 ? super Type 这样的泛型,称之为下界通配符(Lower Bounds Wildcards)

为什么参数 plate 的 get 方法不能使用了?因为Plate<? super Fruit> plate这里说明了plate中适配的是Fruit类和它的父类;我们通过get 方法返回的当然不一定是Fruit类,自然就不能使用啦!

我们可以简单理解为上界通配符的泛型存放(适配)的是该类型的子类们。

下界通配符 (super) 存放(适配) 该类型和它的父类们。所以对于 Plate<? super Fruit> 只能放进 Fruit 和 Food。

4、小结

(1)、上界通配符的泛型存放(适配) 的是该类型的和它的子类们,下界通配符存放(适配) 的是该类型和它的父类们。

(2)、PECS(Producer Extends, Consumer Super)

上界通配符一般用于读取,下界通配符一般用于修改。比如 Java 中 Collections.java 的 copy 方法:

public static <T> void copy(List<? super T> dest, List<? extends T> src) {int srcSize = src.size();if (srcSize > dest.size())throw new IndexOutOfBoundsException("Source does not fit in dest");if (srcSize < COPY_THRESHOLD ||(src instanceof RandomAccess && dest instanceof RandomAccess)) {for (int i=0; i<srcSize; i++)dest.set(i, src.get(i));} else {ListIterator<? super T> di=dest.listIterator();ListIterator<? extends T> si=src.listIterator();for (int i=0; i<srcSize; i++) {di.next();di.set(si.next());}}
}

dest 参数只用于修改,src 参数用于读取操作,只读 (read-only)

通过泛型的协变逆变来控制集合是只读,还是只改。使得程序代码更加优雅。

Java 泛型的不变性 (invariance)、协变性 (covariance)、逆变性 (contravariance)相关推荐

  1. java 协变性_Java 泛型 协变性、逆变性

    Java 泛型 协变性.逆变性 @author ixenos 摘要:协变性.协变通配符.协变数组.协变返回值 协变性.逆变性和无关性 在面向对象的计算机程序语言中,经常涉及到类型之间的转换,例如从具体 ...

  2. [转载] JAVA泛型杂谈--擦除,协变,逆变,通配符等

    参考链接: Java中的协变返回类型 在<JAVA核心思想>这本书里,关于泛型的章节意外的很多,小小的泛型里其实有很多可以学习的内容,我总结下最近看书的成果. 一. 泛型的好处和应用 最基 ...

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

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

  4. Java泛型的不变性和作用域

  5. c#中关于协变性和逆变性(又叫抗变)帮助理解

    今天回忆了之前看的<深入理解C#>这本书中的泛型章节,其中对泛型的可变性的理解.泛型可变性分两种:协变和逆变.逆变也又称为抗变. 怎么理解这两个名词的意思: ①:协变即为在泛型接口类型中使 ...

  6. Java泛型的协变与逆变

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

  7. Java 泛型(Generics) 综述

    一. 引子 一般的类和方法,只能使用具体类型:要么是基本类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大. 多态 算是一种泛化机制,但对代码的约束还是太强 ...

  8. Java 泛型的协变与逆变

    协变.逆变.抗变 协变,逆变,抗变等概念是从数学中来的,在编程语言Java/Kotlin/C#中主要应用在泛型上.描述的是两个类型集合之间的继承关系. 第一个集合为:Animal.Dog , Dog ...

  9. java泛型的逆变_Java泛型的逆变

    在上篇<Java泛型的协变>这篇文章中遗留以下问题--协变不能解决将子类型添加到父类型的泛型列表中.本篇将用逆变来解决这个问题. 实验准备 我们首先增加以下方法,见代码清单1所示. 代码清 ...

最新文章

  1. python项目设计-Python:实际项目中抽象出的小项目设计
  2. TensorFlow在windows 下的安装
  3. DCOM 示例:演示如何远程调用 COM 对象
  4. mysqldump导出数据库 (dos环境下)
  5. windows下的diskpart指令彻底格式化清除U盘
  6. POI读取Excel内容格式化
  7. Collection集合 创建/添加/迭代
  8. Java 8 方法引用 (Method Reference)快速介绍与示例
  9. OCR文本扫描 轮廓检测 透视变换-唐宇迪笔记
  10. 如何做决策?SWOT分析
  11. Android报警功能,报警铃音,手机开始震动
  12. 要闻丨腾飞•新征程,人大金仓完成近亿元融资
  13. 计算机硬件技术基础 试题与答案,计算机硬件技术基础网上作业及答案
  14. 短信网关在短信信息服务中的作用(转)
  15. 鸿蒙系统为万物互联而生,鸿蒙系统发布:万物互联 EMUI10同台上场
  16. 用webgl绘制一个彩色旋转立方体
  17. MongoDB高性能、高可用之副本集、读写分离、分片、操作实践
  18. spring boot注解@PostConstruct
  19. 易读代码的艺术之Code Should Be Easy to Understand
  20. ERP系统对接方案,API接口封装系列(高并发)

热门文章

  1. caffe将用训练好的caffemodel和train_val.prototxt文件分类新的一张图片-下篇--caffe学习(7)
  2. 深度学习基础:基于人脸的常见表情识别(2)—数据获取与整理
  3. 基于物理的渲染—迪士尼的渲染模型
  4. 关闭数字健康 android 魅族,魅族Flyme数字健康,手机使用情况尽收眼底,还能限制使用!...
  5. 关于-fPIC, -fpic, -fpie, -fPIE的一点理解
  6. JavaScript-150:分时间问候并显示不同图片案例
  7. OpenIM:如何打造安全可靠的即时通讯服务
  8. New关键字(实例化对象)
  9. c语言 机构体传给指针,注意使用结构体指针给函数传递参数。
  10. 用WheatA获取农业大数据