[改善Java代码]警惕泛型是不能协变和逆变的

2023-10-29

什么叫做协变(covariance)和逆变(contravariance)?

在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是用一个窄类型替换宽类型,而逆变则是用宽类型覆盖窄类型.

协变:宽类型------>窄类型

逆变:窄类型------>宽类型

class Base{
    public Number doStuff(){
        return 0;
    }
}

class Sub extends Base{
    @Override
    public Integer doStuff(){
        return 0;
    }
}

子类的 doStuff 方法返回值的类型比父类方法要窄(Integer extend Number),此时 doStuff 方法就是一个协变方法,同时根据 Java 的覆写定义来看,这又属于覆写。那什么是逆变呢?代码如下:

class Base{
    public void doStuff(Integer i){    
    }
}

class Sub extends Base{
    public void doStuff(Number n){    
    }
}

子类的 doStuff 方法的参数类型比父类要宽,此时就是一个逆变方法,子类扩大了父类方法的输入参数,但是根据覆写定义来看,doStuff 不属于覆写,只是重载而已。

由于此时的doStuff方法已经与父类没有任何关系了,只是子类独立扩展出的 一个行为,所以是否声明为doStuff方法名意义不大,逆变已经不具有特别的意义了.我们来重点关注一下协变.

public class Client {
    public static void main(String[] args) {
        Base base = new Sub();
    }
}

 

 base变量是否发生了协变?是的....发生了协变,base变量是Base类型,它是父类,而其赋值却是子类实例,也就是用窄类型覆盖了宽类型,这也叫做多态(Polymorphism),两者相同含义,在Java世界里,"重复发明"轮子的事情多了去了.

 

泛型是既不支持协变也不支持逆变.为什么?

(1)泛型不支持协变

数组和泛型很相似,一个是中括号,一个是尖括号,那我们就以数组为参照对象,看代码:

public class Client {
    public static void main(String[] args) {
        //数组支持协变
        Number[] n = new Integer[10];
        //编译不通过,泛型不支持协变
        //List<Number> ln = new ArrayList<Integer>();//报错
        //Type mismatch: cannot convert from ArrayList<Integer> to List<Number>
    }
}

 

ArrayList是List的子类型,Integer是Number的子类型,里氏替换原则在此行不通了,原因就是Java为了保证运行期的安全性,必须保证泛型参数类型是固定的.

所以它不允许一个泛型参数可以同时包含两种类型,即使是父子类关系也不行.

泛型不支持协变,但可以使用通配符(Wildcard)模拟协变.代码如下:

        //Number的子类型都可以是泛型参数类型
        List<? extends Number> ln = new ArrayList<Integer>();

 

"? extends Number" 表示的意思是,允许Number所有的子类(包括自身)作为泛型参数类型,但在运行期只能是一个具体类型,或者是Integer或者是Double类型,

或者是Number类型,也就是说通配符只是在编码期有效,运行期必须是一个确定类型.

(2)泛型不支持逆变

Java虽然可以允许逆变存在,单在对类型赋值上是不允许逆变的,你不能把一个父类实例对象赋值给一个子类类型变量,泛型自然也不允许此种情况发生了,

但是它可以使用super关键字来模拟实现.代码如下:

        //Integer的父类型(包括Integer)都可以是泛型参数类型
        List<? super Integer>  li = new ArrayList<Number>();

 

"
"? super Integer" 的意思是可以把所有Integer父类型(自身,父类或接口)作为泛型参数,这里看着就像是把一个Number类型的ArrayList赋值给了Integer类型的List,
其外观类似于使用一个宽类型覆盖一个窄类型,它模拟了逆变的实现.
泛型既不支持协变也不支持逆变,带有泛型参数的子类型定义与我们经常使用的类类型也不相同,其基本的类型关系如下:

泛型通配符的Q&A
1.Integer是Number的子类型 √
2.ArrayList<Integer>是List<Integer>的子类型 √
3.Integer[] 是Number[] 的子类型 √
4.List<Integer> 是List<Number>的子类型 ×
5.List<Integer> 是List<? extends Integer>的子类型 ×
6.
List<Integer> 是List<? super Integer>的子类型  ×

 

Java的泛型不支持逆变和协变,只是能够实现逆变和协变.

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[改善Java代码]警惕泛型是不能协变和逆变的 的相关文章

  • 唯一索引或主键违规:“PRIMARY KEY ON PUBLIC.xxx”; SQL语句

    每当我的应用程序启动时 我都会收到以下错误消息 Caused by org h2 jdbc JdbcSQLException Unique index or primary key violation PRIMARY KEY ON PUBL
  • 如何在日期选择器中设置不在当前月份的单元格的样式

    我目前正在为我的 JavaFX 应用程序制作注册表 问题是 当日期选择器中的单元格不在页面的月份上时 我想让该单元格变灰 让我们看看我当前的日期选择器 我的日期选择器 正如您所看到的 我希望下个月的日期 27 日 28 日 30 日以及 1
  • 如何使用 FileChannel 将一个文件的内容附加到另一个文件的末尾?

    File a txt好像 ABC File d txt好像 DEF 我正在尝试将 DEF 附加到 ABC 所以a txt好像 ABC DEF 我尝试过的方法总是完全覆盖第一个条目 所以我总是最终得到 DEF 这是我尝试过的两种方法 File
  • AES 加密 Java/plsql

    我需要在Java和plsql DBMS CRYPTO for Oracle 10g 上实现相同的加密 解密应用程序 两种实现都工作正常 但这里的问题是我对相同纯文本的加密得到了不同的输出 下面是用于加密 解密过程的代码 Java 和 PLS
  • ExceptionConverter:java.io.IOException:文档没有页面。我正在使用 iText

    当我执行下面的代码时 File f new File c sample pdf PdfWriter getInstance document new FileOutputStream f document open System out p
  • CXF Swagger2功能添加安全定义

    我想使用 org apache cxf jaxrs swagger Swagger2Feature 将安全定义添加到我的其余服务中 但是我看不到任何相关方法或任何有关如何执行此操作的资源 下面是我想使用 swagger2feature 生成
  • 使用 ANTLR 为 java 源代码生成抽象语法树

    如何使用 ANTLR 从 java src 代码生成 AST 有什么帮助吗 好的 步骤如下 前往ANTLR站点 http www antlr org 并下载最新版本 下载Java g和JavaTreeParser g文件来自here htt
  • Convert.FromBase64String 方法的 Java 等效项

    Java 中是否有相当于Convert FromBase64String http msdn microsoft com en us library system convert frombase64string aspx which 将指
  • 如何在 Java 中禁用 System.out 以提高速度

    我正在用 Java 编写一个模拟重力的程序 其中有一堆日志语句 到 System out 我的程序运行速度非常慢 我认为日志记录可能是部分原因 有什么方法可以禁用 System out 以便我的程序在打印时不会变慢 或者我是否必须手动检查并
  • 如何将文件透明地传输到浏览器?

    受控环境 IE8 IIS 7 ColdFusion 当从 IE 发出指向媒体文件 例如 mp3 mpeg 等 的 GET 请求时 浏览器将启动关联的应用程序 Window Media Player 我猜测 IIS 提供文件的方式允许应用程序
  • 反思 Groovy 脚本中声明的函数

    有没有一种方法可以获取 Groovy 脚本中声明的函数的反射数据 该脚本已通过GroovyShell目的 具体来说 我想枚举脚本中的函数并访问附加到它们的注释 Put this到 Groovy 脚本的最后一行 它将作为脚本的返回值 a la
  • 归并排序中的递归:两次递归调用

    private void mergesort int low int high line 1 if low lt high line 2 int middle low high 2 line 3 mergesort low middle l
  • 制作java包

    我的 Java 类组织变得有点混乱 所以我要回顾一下我在 Java 学习中跳过的东西 类路径 我无法安静地将心爱的类编译到我为它们创建的包中 这是我的文件夹层次结构 com david Greet java greeter SayHello
  • 使用 AWS Java SDK 为现有 S3 对象设置 Expires 标头

    我正在更新 Amazon S3 存储桶中的现有对象以设置一些元数据 我想设置 HTTPExpires每个对象的标头以更好地处理 HTTP 1 0 客户端 我们正在使用AWS Java SDK http aws amazon com sdkf
  • Java直接内存:在自定义类中使用sun.misc.Cleaner

    在 Java 中 NIO 直接缓冲区分配的内存通过以下方式释放 sun misc Cleaner实例 一些比对象终结更有效的特殊幻像引用 这种清洁器机制是否仅针对直接缓冲区子类硬编码在 JVM 中 或者是否也可以在自定义组件中使用清洁器 例
  • Tomcat 6找不到mysql驱动

    这里有一个类似的问题 但关于类路径 ClassNotFoundException com mysql jdbc Driver https stackoverflow com questions 1585811 classnotfoundex
  • 将 JTextArea 内容写入文件

    我在 Java Swing 中有一个 JTextArea 和一个 提交 按钮 需要将textarea的内容写入一个带有换行符的文件中 我得到的输出是这样的 它被写为文件中的一个字符串 try BufferedWriter fileOut n
  • 将2-3-4树转换为红黑树

    我正在尝试将 2 3 4 树转换为 java 中的红黑树 但我无法弄清楚它 我将这两个基本类编写如下 以使问题简单明了 但不知道从这里到哪里去 public class TwoThreeFour
  • java迭代器内部是如何工作的? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我有一个员工列表 List
  • java8 Collectors.toMap() 限制?

    我正在尝试使用java8Collectors toMap on a Stream of ZipEntry 这可能不是最好的想法 因为在处理过程中可能会发生异常 但我想这应该是可能的 我现在收到一个我不明白的编译错误 我猜是类型推理引擎 这是

随机推荐

  • 博图db块变量导出_博途中,DB里定义的变量的改变

    问题描述 1 新的博途中 生成一个全局DB 打开 在第一行自动生成了 名称 为static的变量 名称无法修改 数据类型无法选择 而在老版的STEP7里 分明是一个无名称 数据类型是STRUCT 无法重新选择 的变量 为什么做了这样的改变
  • 理解super().__init__()

    目录 一 写在前面 本文仅为个人的理解 如有错误欢迎指正 二 super init 的含义 三 代码实验 四 理解super init 五 Reference 一 写在前面 本文仅为个人的理解 如有错误欢迎指正 二 super init 的
  • AdaGrad(自适应梯度算法),Adaptive

    学习衰减率 随着学习的进行 使得学习率逐渐减小 AdaGrad会为参数的每个元素适当的体哦阿正学习率 coding utf 8 import numpy as np class AdaGrad def init self learning
  • template模板:泛型化编程

    一 在函数中使用模板 template
  • macbookpro接口叫什么_了解这些常用接口一定会有用的

    日常使用手机 电脑以及其他电子产品 免不了要跟各种接口打交道 周末花了些时间查了些资料 并总结自己的实际使用经验 跟大家聊一聊我们日常使用手机 iPad 电脑 外设中常用到的接口 讲明白各种接口是一件非常复杂的事情 我会尽量只写日常中常见到
  • 【深度学习】AAAI 2024,14000篇投稿,大家都写了啥?

    夕小瑶科技说 原创作者 python 8月16日截稿的AAAI 2024 从投稿ID看 已超14000篇投稿 这么多投稿 大家都写了啥 今年什么话题最火 和往年相比 今年的投稿趋势又有什么变化 本文中 小编通过对比AAAI 2024与202
  • 文本分类(六):使用fastText对文本进行分类--小插曲

    需要注意的问题 1 linux mac 平台 2 标签中的下划线是两个 两个 两个 环境说明 python2 7 linux 自己打自己脸 目前官方的包只能在linux mac环境下使用 误导大家了 对不起 测试facebook开源的基于深
  • React全家桶-react-router原理实现

    29 9React课程 第05节 react全家桶 第5节 react全家桶 第5节 react全家桶 App js exact精确匹配 可以使用switch独占如果有一个匹配不会继续往下走 一般要与exact配合使用 否则下面不会执行 f
  • c++显示实例化和显示具体化

    1 实例化 instantiation 实例化是指编译器使用函数 或者是类 模板为特定类型生成函数 类 定义 编译器不会为函数 或者类 模板生成定义 只有当我们为函数 或者类 模板指定了一个特定类型时 编译器才会生成 编译器为特定类型的函数
  • git克隆项目时用户名密码是什么_git clone github项目的两种方式(http协议和ssh 协议)介绍...

    简介 在使用git来进行版本控制时 为了得一个项目的拷贝 copy 我们需要知道这个项目仓库的地址 Git URL Git能在许多协议下使用 所以Git URL可能以ssh http s git 或是只是以一个用户名 git 会认为这是一个
  • Google 奔跑吧小恐龙

    Google 奔跑吧小恐龙 在Java中 可以使用JavaFX图形框架来实现游戏界面 使用多线程技术来实现游戏循环和动画效果 以下是使用JavaFX和多线程实现奔跑吧小恐龙游戏的代码示例 1 创建游戏面板并初始化游戏元素 public cl
  • python:解决pycharm运行py文件时只有unittest选项的方法

    有时候在编完脚本开始运行时 发现某个py脚本右键运行的选项不是run 二是run in unittest 试过很多方法都不能很好的去除 主要是因为脚本中含有test字符串 一种解决方法是将脚本中所有的函数和类的test字符串改为其他的 但是
  • 二维光子晶体带隙仿真Matlab完全程序_平面波展开法

    本程序为二维光子晶体Matlab仿真程序 该结果与文献 1 Molding the flow of light p68 figure 2相互吻合 主程序 This is a simple demo for Photonic Crystals
  • MinIO

    文章目录 安装 依赖 使用 下载地址 链接 https pan baidu com s 1V qK13gpddcnlq czc v3A 提取码 yyds 安装 docker run d p 9000 9000 p 9001 9001 nam
  • 【MySQL(一)】WAL 机制

    WAL全称是write ahead log 也就是更新数据之前先更新日志 之前不太明白为什么要用这个 也查了很多博客 发现很多都没说到根本原因上 基本的解释都是什么使用redo log恢复数据之类的 其实Mysql使用WAL的原因根本就不是
  • vue 不能监听对象属性的改变

    vue 不能监听对象属性的改变 需要重新赋值后才会渲染页面 div item div 方法 commonClick index this active index this active index
  • post和get的区别

    1 get是从服务器上获取数据 post是向服务器传送数据 2 get是把参数数据队列加到提交表单的ACTION属性所指的URL中 值和表单内各个字段一一对应 在URL中可以看到 post是通过HTTP post机制 将表单内各个字段与其内
  • 表驱动法(Table-Driven Methods)

    背景 表驱动方法是一种方案 它允许您在表中查找信息 而不是使用逻辑语句 if和 case 来查明信息 实际上 您可以使用逻辑语句选择的任何内容 都可以使用表进行选择 在简单情况下 逻辑语句更容易 更直接 随着逻辑链变得越来越复杂 表格变得越
  • shell中$(( )) 与 $( ) 还有${ }的区别

    http blog zol com cn 2322 article 2321763 html 与 反引号 在 bash shell 中 与 反引号 都是用来做命令替换用 command substitution 的 所谓的命令替换与我们第五
  • [改善Java代码]警惕泛型是不能协变和逆变的

    什么叫做协变 covariance 和逆变 contravariance 在变成语言的类型框架中 协变和逆变是指宽类型和窄类型在某种情况下 如参数 泛型 返回值 替换或交换的特性 简单的说 协变是用一个窄类型替换宽类型 而逆变则是用宽类型覆