Java 泛型之-协变&逆变

Java中的逆变与协变 逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果AAABBB表示类型,f⋅f(\cdot)f⋅表示类型转换,≤\leq≤表示继承关系(比如,A≤BA \leq BA≤B表示AAA是由BBB派生出来的子类);f⋅f(\cdot)f⋅是逆变(contravariant)的,当A≤BA \leq BA≤B时有fB≤fAfB≤fA成立; 阅读详情

首先,Java有泛型这一个概念,初衷是为了保证在运行时出现的错误能提早放到编译时检查。有了这个前提,再来看看题主的问题。

先说一下java中的协变,逆变,不可变:

假设有如下类:

class Food{} // 默认继承Object
class Fruit extends Food{}
class Meat extends Food {}

class Apple extends Fruit{}
class Beef extends Meat{}

这里写图片描述

逆变与协变

Java中String类型是继承自Object的,姑且记做String ≦ Object,表示String是Object的子类型,String的对象可以赋给Object的对象。而Object的数组类型Object[],理解成是由Object构造出来的一种新的类型,可以认为是一种构造类型,记f(Object),那么可以这么来描述协变和逆变:

  • 当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变;
  • 当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变;
  • 如果上面两种关系都不成立则叫做不可变。

例如数组是斜变得:

Food food = new Fruit();  
// or
food = new Meat(); // 即 把子类赋值给父类引用

Fruit [] arrFruit = new Fruit[3];
Food [] arrFood = arrFruit; // 数组协变的

java 的泛型是不可变的:

List<Beef> beefList = new ArrayList<>();
List<Food> foodList = beefList; //错误:不可协变  
beefList = foodList; // 错误 : 不可逆变

eat(beefList);// 错误::不可协变  

public void addFood(List<Food> list){
    list.add(new Apple());
}

为什么不能把beefList 作为参数传递给 addFood(List<Food> list) 方法呢?这儿我们用反正法类证明:

  1. addFood 方法接收List<Food> list 的类型参数,那么在addFodd 方法中能给 传递进来的List add 任何Food 的子类型。那么,既然eat(beefList) 这行代码没有问题,也就意味着能通过addFood 方法给 beefList 里面增加任何 Food 的子类型(比如:list.add(new Apple())).
  2. 通过第一步,我们给 装肉beefList 里面加了一个苹果(Apple), 那么从beefList get 数据赋值给Beef 的时候(Beef beef = beefList.get(0))就会发生类型转换异常 ,因为取出来的是一个Apple, 这样明显有问题了。

因此:得出java中泛型是不变得,既不能协变,也不能逆变。

泛型中的通配符实现协变与逆变

JAVA中泛型是不变的,可有时需要实现逆变与协变,怎么办呢?这时就需要通配符?。

<? extends E>实现了泛型的协变,比如:

List<? extends Food> foodList = new ArrayList<>();
List<Apple> appleList = new ArrayList<>();
foodList = appleList; // ok 协变

foodList.add(new Beef()); // 错误 不能执行添加null 以外的操作
foodList.add(new Food());// 错误,同上,
foodlist.add(new Apple()); // 错误,同上

Food food = foodList.get(index); //ok, 把子类引用赋值给父类显然是可以的

<? extends Food> 指明了上界,即表示了集合中存放的对象是 Food 或者Food 的子类,因此foodList 就表示了一个存放 Food 或者Food 的子类的集合,appleList 集合就是一个存放Food 的子类 Apple的集合, 因此可以把 appleList 赋值给 foodList,但是不能对foodList 添加除null 以外的任何对象。为什么不能添加呢?其实很简单,如果可以允许的话,那么foodList 就可以添加Food 或者Food 的子类型,那么上面代码中在foodList = appleList; 赋值后,执行foodList.add(new Beef()); 操作就会导致给 appleList 里面添加了一个Beef 对象,显然这样是不对的。因此可以得出结论:

List<? super Fruit> fruitList = new ArrayList<>();
List<Food> foodList = new ArrayList<>();
fruitList = foodList; // ok 逆变

foodList.add(new Meat()); 

fruitList.add(new Apple()); // ok,只能添加 Fruit 或者 其子类
fruitList.add(new Food());// error, 只能添加 Fruit 或者 其子类

Fruit fruit = fruitList.get(0); // error,get出来的元素是Object类型
Object obj = fruitList.get(0);// ok 

<? super Fruit> 指明了下界,即表示了集合中存放的对象只能是 Fruit 或者其父类,因此fruitList 表示了一个存放Fruit 或者其父类的集合,那么就可以把一个Fruit 父类Food 的集合foodList 赋值给 fruitList(即:fruitlist = foodList),这儿可能有的人就有点疑问了?<? super Fruit> 指明了集合存放的对象是Fruit 或者其父类,那为什么往集合中添加Fruit的父类元素不行呢(fruitList.add(new Food()))?同样我们用反正法来说明,假如可以向<? super E> 集合中添加 E 的父类,那么就可以向fruitList 添加Fruit 的父类(Food or Object),在本例中,我们把foodList 赋值给了fruitList(fruitList = foodList),既然可以添加父元素,那么我们执行fruitList.add(new Food) 就相当于给 foodList集合里面添加了一个Food元素,这看起来没有什么问题,因为foodList 本来就是用来装Food 元素的,但是如果我们添加的Fruit 父类是Object 对象(或者一个Fruit 实现的和Food 无关的接口,又或者Food继承的一个其他类B,那么B也是 Fruit 的父类),那么就意味着给foodList 里面添加了和Food 无关的类,这样显然是不行的。那么为什么往fruitList 里面添加Fruit或者其子类 元素是可以的,因为fruitList 表示的就是一个存放Fruit 或者其父类的集合,所以这个集合肯定就能装Fruit 或者其子类,比如上面把foodList 赋值给fruitList 后,往fruitList 里面添加 Apple 就相当于往foodList 里面添加Apple,显然是可以的,对于get 操作时为什么读取出来的是Object ,是因为 fruitList 集合表示的存放Fruit 或者其父类的集合,而Fruit 的父类可能有很多,在本例中由于我们知道存的是Food(fruitList = foodList), 但是在实际中,谁知道在运行时到底存的是哪一个呢?因此为了安全全部定义为Object 类型。因此得出结论:

<? super E> 支持逆变,能往集合中添加E 或者E子类的对象。不能添加E的任何父类对象,读取时的对象为Object 类型,因此

ZEMAX知识点:坐标间断面(coordinate break) zemax知识点:坐标间断面 阅读详情

相关推荐

在树莓派上部署训练好的YOLO模型

.pt.模型转换为onnx格式的模型,树莓派上部署模型

pai_da_xing8的博客 3887

Java 泛型的协变与逆变

Java 泛型中的协变与逆变

qq_38074398的博客 1847

PX4飞控与MAVROS实战:从零搭建无人机ROS通信环境(附常见报错解决)

本文详细介绍了如何从零搭建PX4飞控与ROS的通信环境,重点解析了MAVROS的安装、硬件连接、串口权限配置以及坐标系转换等核心步骤。针对GeographicLib数据下载失败、串口权限不足等常见报错提供了实用解决方案,并附有Offboard模式控制的代码示例,帮助开发者快速建立稳定的无人机ROS开发环境。

dapp9builder的博客 335

Java泛型,数组和方法返回类型 - 协变,逆变和不变

协变vs逆变vs双变vs不变 Java支持不同的子类型规则:数组是协变的,可以从子类型转换为超类型,但可能引发ArrayStoreException。泛型默认是不变的,特定类型T的泛型不支持协变或逆变替换。通过使用通配符'?'和'extends'或'super',可以使泛型实现协变或逆变。此外,Java允许子类方法返回更具体的类型,这被称为协变返回类型。

qingchen191的专栏 1259

Word公式批量转MathType终极指南:解决omml2mml.xsl缺失与格式调整难题

本文提供Word公式批量转MathType的终极解决方案,重点解决常见的omml2mml.xsl文件缺失问题,并详细讲解批量转换与格式调整技巧。通过补全文件路径、分批次转换及样式预设等方法,大幅提升学术论文公式编辑效率,适用于Office 2016及以上版本。

weixin_28419039的博客 1349

【Python计量】linearmodels面板数据回归(一)

我们以伍德里奇《计量经济学导论现代方法》的”第14章高级面板数据方法“的案例14.1为例,使用jtrain中的数据来进行固定效应估计。

mfsdmlove的博客 9277

Java 泛型的型变 (协变、逆变) 和不变

通过PageVO引出的泛型思考: 首先声明: java中对于泛型的型变只是一种约束; 和kotlin等其他语言并不一致. 型变: 分为逆变和协变, 与不变对应 协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。 逆变(contravariant),如果它逆转了子类型序关系。 不变(invariant),如果上述两种均不适用。 假设Orange类是Fruit类的子类,以集合类List为例: 型变:用来描述类型转换后的继承关系(即协变、逆变和不变的统称)。比如:List

howeres的博客 4297

【部署】OpenClaw版本降级实操(含全量卸载)

本文详细介绍了OpenClaw工具的降级与卸载方法。主要内容包括:1)通过npm安装指定版本的核心命令及国内镜像加速方案;2)两种卸载路径:CLI工具存在时的简易一键卸载(含交互式与非交互式命令)和手动分步卸载流程;3)CLI已删除但服务仍在运行时,针对macOS(launchd)、Linux(systemd)和Windows(计划任务)系统的具体清理步骤。文章特别强调了卸载前备份的重要性,并提供了不同安装方式(常规安装与源码克隆)的额外说明,确保用户能彻底清理所有组件。

海兰 3171

协变 (Covariant)、逆变 (Contravariant) 与不变 (Invariant)

协变(Covariant)、逆变(Contravariant)与不变(Invariant)1. 定义2. 例子3. 有什么用?4. 应对5. 其他问题6. 特例(Dart 语言)7. 写在最后8. 参考 1. 定义 协变与逆变 (Covariance and contravariance) 是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语。 协变、逆变和不变,属于变型 (variance) 的三种结果,变型指类型构造器如何根据内部类型关

B1151937289的博客 1万+

基于Simulink的四足机器人楼梯攀爬仿真模型

构建一个基于Simulink的四足机器人楼梯攀爬仿真模型,旨在模拟四足机器人在面对楼梯等障碍时的运动控制和稳定性调整。以下是一个详细的步骤指南,帮助你从零开始创建这样一个仿真模型。

xiaoheshang_123的博客 247

基于STM32的室内环境监测系统设计与实现

摘要 本文设计了一种基于STM32的室内环境监测系统,通过传感器实时采集温湿度、空气质量、烟雾浓度等数据,并利用Wi-Fi模块上传至云服务器。系统采用STM32F103C8T6作为主控芯片,结合DHT11、MQ系列传感器及BMP280气压传感器实现数据采集,通过ESP8266模块实现远程数据传输。用户可通过手机APP实时查看环境参数,并在数据超标时触发报警。实验表明,系统运行稳定,监测精度高,具有低成本、易扩展等特点,为改善室内环境质量提供了有效解决方案。 关键词:STM32;室内环境监测;传感器;Wi-F

3254968421的博客 221

Richard Warburton与Raoul-Gabriel Urma在Devoxx UK 2015上回顾Java泛型历史

Richard与Raoul向广大开发者提供关于Java 8的培训课程。近日,二人在Devoxx UK 2015上共同进行了一场演讲,回顾了Java泛型的起源与动机、介绍了目前一些不为人知的特性,同时对Java 10进行了展望。该演讲由3个精彩的部分构成:泛型的过去、现在与未来。\\过去\\正如Richard与Raoul所说,在引入泛型之前,Java中的集合是很难使用的,因为其中包含的是Object...

cpongo5 140

C-08-实战:搭建一个AI客服机器人(接入微信企微)

客服是一人公司最大的时间杀手。本文教你搭建一个AI客服机器人,能自动回答80%的常见问题,只把复杂问题转给人工。含微信公众号和企业微信接入完整代码。

Johnny2004的专栏 87

scala模式匹配-异常-泛型-隐式转换

文章目录1.模式匹配基本语法模式守卫模式匹配类型匹配对象样例类异常处理scala异常护理隐式转换泛型协变和逆变泛型上下限上下文限定 1.模式匹配 基本语法 Scala 中的模式匹配类似于 Java 中的 switch ,但是 scala 从语法中补充了更多的功能,所以更加强大。 java中如下: int i = 10 switch (i) { case 10 : System.out.println("10"); break; case 20 : System.out.println("20"); b

weixin_46155444的博客 464

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

新JEP将简化Java类型变异

新的JEP Candidate旨在简化处理Java中复杂的类型变异的概念。这个新的JEP Candidate可能会在Java 10中推出,提供了在定义的泛型类型中指定目标对象默认变异的方法,而不是在泛型类型实例化时通过通配符指定。这个新方案并不会代替通配符,而是减少对通配符的需求。\\类型变异这个概念对于很多开发人员来说仍然比较模糊,在Java中通过不太普及的通配符来解决这个问题并没有很大帮助。因...

weixin_34107739的博客 84

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Scala——模式匹配、异常、隐式转换、泛型

Scala——模式匹配、异常8 模式匹配8.1 基本语法8.2 模式守卫8.3 模式匹配类型9 异常10 隐式转换10.1 隐式函数10.2 隐式类10.3 隐式参数11 泛型11.1 协变和逆变11.2 泛型上下限11.3 上下文限定 8 模式匹配 8.1 基本语法 模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明,当需要匹配时,会从第一个 case 分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有 case

qq_42873479的博客 800

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Scala基础复习(1)

Scala 简介 Scala 是 Scalable Language 的简写,是一门多范式的编程语言 联邦理工学院洛桑(EPFL)的Martin Odersky于2001年基于Funnel的工作开始设计Scala。 Funnel是把函数式编程思想和Petri网相结合的一种编程语言。 Odersky先前的工作是Generic Java和javac(Sun Java编译器)。Java平台的Scala于2003年底/2004年初发布。.NET平台的Scala发布于2004年6月。该语言第二个版本,v2.0,发布于

AnnerLi的博客 425

隐式转换代码

测试;类 初学者一定要知道函数与好方法的书写格式 函数,变量类型val var 变量名 =(一个或多个参数列表) =&gt; { 函数体 } 方法: 关键字 方法名 (参数列表) = {方法体} import java.io.File //这个导入方法可以放在组外面,这样下面所有涉及到的方法都可以用 import Demo05.MyContent01._ object FileDemo { def main(args: Array[String]): Unit = { ...

LEOZHYD的博客 234

HIT SC blog-reuse,maintainability

HIT SC blog5

m0_52200890的博客 240

Arnold变换+Java实现

编程实现Arnold变换1.概念Arnold变换是由Arnold提出的一种变换,也称猫变换、猫映射。目前Arnold变换主要用于图像置乱。该变换可以在其它图像处理前对图像做预处理,例如在数字盲水印嵌入前对水印进行置乱。也可以用于普通的图像加密。(1).正变换对于规模为NXN的正方形数字图像来说,我们将图像的所有像素集合看作一个NXN的矩阵。在Arnold变换中,设该矩阵中的任意像素位置为(x,y),

考拉研究僧的博客 9236

Go / C / Python / Java 四语言对照学习总览

按统一维度横向对比四门语言的核心知识点,便于交叉记忆与迁移学习。

2401_83227843的博客 949

C#语言基础

一些主流编程语言1.C:嵌入式硬件开发2.C++:游戏客户端,服务器,软件3.C#:游戏客户端,服务器,软件,网站4.Java:安卓,服务器,软件,网站5.JavaScript:网站,服务器6.PHP:网站,服务器7.Python:网站,服务器,辅助开发8.SQL:数据库9.Go:服务器10.:mac,ios开发11.Swift:mac,ios开发使用IDE。

qq_55071342的博客 1890

罗素“类型论“与计算机程序设计艺术4 / By 陈光剑&AI天才研究院

在面向对象编程中,类不仅是创建对象的模板,也可以被视为一种类型。这种观点将类型论的概念应用于面向对象编程,为理解和形式化面向对象系统提供了强大的工具。类作为类型的定义:类定义了一组操作(方法)和状态(属性),可以看作是一种复合类型。形式化表示: Class=(Attributes,Methods)\text{Class} = (\text{Attributes}, \text{Methods})Class=(Attributes,Methods) 其中, Attributes={(a1:τ1),…,(an:

AI天才研究院 919

指数分布java_指数分布的随机数

一、功能产生指数分布的随机数。二、方法简介1、产生随机变量的逆变换法定理设 \(F(x)\) 是任一连续的分布函数,如果 $ u \sim U(0, \ 1) $ 且 $ \eta \sim F(x) $。证明由于$ u \sim U(0, \ 1) $,则有\[P(\eta \leqslant x)=P(F^{-1}(u)\leqslant x)=P(u\leqslant F(x))=F(x)\...

weixin_33736386的博客 503
上一篇: @TypeQualifierDefault
下一篇: Android Studio 插件无法下载或者无法浏览
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮克桃在写代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值