当前位置: 首页 > news >正文

Effective Java笔记(28)列表优于数组

        数组与泛型相比,有两个重要的不同点 。 首先,数组是协变的( covariant ) 。 这个词听起来有点吓人,其实只是表示如果 Sub 为 Super 的子类型,那么数组类型 Sub[ ]就是Super[ ]的子类型。 相反,泛型则是可变的( invariant ):对于任意两个不同的类型 Type1 和 Type2,List<Type1>既不是 List < Type2 > 的子类型,也不是 List<Type2 >的超类型。 你可能认为,这意味着泛型是有缺陷的,但实际上可以说数组才是有缺陷的 。 下面的代码片段是合法的 :

Object[] objectArray = new Long[1] ;
objectArray[0] = "I don't fit in";

        但下面这段代码则不合法:

List<0bject> o1 = new ArrayList<Long>();
o1.add("I don't fit in");

        这其中无论哪一种方法,都不能将 String 放进 Long 容器中,但是利用数组,你会在运行时才发现所犯的错误;而利用列表,则可以在编译时就发现错误 。 我们当然希望在编译时就发现错误 。

        数组与泛型之间的第二大区别在于,数组是具体化的。因此数组会在运行时知道和强化它们的元素类型。如上所述,如果企图将 String 保存到 Long 数组中,就会得到一个 ArrayStoreException 异常 。 相比之下,泛型则是通过擦除( erasure)来实现的 。 这意味着,泛型只在编译时强化它们的类型信息,并在运行时丢弃(或者擦除)它们的元素类型信息 。 擦除就是使泛型可以与没有使用泛型的代码随意进行互用,以确保在 Java 5 中平滑过渡到泛型 。

        由于上述这些根本的区别 ,因此数组和泛型不能很好地棍合使用 。 例如,创建泛型、参数化类型或者类型参数的数组是非法的 3 这些数组创建表达式没有一个是合法的:new List<E > []、new List<String> []和 new E [] 。 这些在编译时都会导致一个泛型数纽创建( generic array creation )错误 。

        为什么创建泛型数组是非法的?因为它不是类型安全的 。 要是它合法,编译器在其他正确的程序中发生的转换就会在运行时失败,并出现一个 ClassCastException 异常 。这就违背了泛型系统提供的基本保证 。

        为了更具体地对此进行说明,以下面的代码片段为例 :

        我们假设第1行是合法的,它创建了一个泛型数组 。 第 2 行创建并初始化了一个包含单个元素的 List< Integer > 。 第 3 行将 List<String>数组保存到一个 Object 数组变量中,这是合法的,因为数组是协变的 。 第 4 行将 List< Integer>保存到 Object 数组里唯一的元素中,这是可以的,因为泛型是通过擦除实现的:List<Integer >实例的运行时类型只是 List, List<String>[ ] 实例的运行时类型则 是 List [],因此这种安排不会产生 ArrayStoreExceptio口异常 。 但现在我们有麻烦了 。 我们将一个 List<Integer>实例保存到了原本声明只包含 List<String>实例的数组 中 。 在第 5 行中,我们从这个数组里唯一的列表中获取了唯一的元素 。 编译器自动地将获取到 的元素转换成 String ,但它是一个 Integer ,因此,我们在运行时得到了一个 ClassCastException 异常。 为了防止出现这种情况,(创建泛型数组的)第1 行必须产生一条编译时错误 。

        从技术的角度来说,像 E 、List<E >和 List<String >这样的类型应称作不可具体化的( nonreifiable )类型。 直观地说,不可具体化的( non-reifiable )类型是指其运行时表示法包含的信息 比它的 编译时表示法包含的信息更少的类型 。 唯一可具体化的( reifiable )参数化类型是无限制的通配符类型,如 List <?>和 Map<?,?>  。虽然不常用,但是创建无限制通配类型的数组是合法的 。

        禁止创建泛型数组可能有点讨厌 。 例如,这表明泛型一般不可能返回它的元素类型数组 。 这也意味着在结合使用可变参数( varargs )方法和泛型时会出现令人费解的警告 。 这是由于每当调用可变参数方法时,就会创建一个数组来存放 varargs 参数 。 如果这个数组的元素类型不是可具体化的( reifialbe ),就会得到一条警告 。 利用 Saf eVarargs 注解可以解决这个问题 。

        当你得到泛型数组创建错误时,最好的解决办法通常是优先使用集合类型 List<E>,而不是数组类型 E[ ] 。 这样可能会损失一些性能或者简洁性,但是换回的却是更高的类型安全性和互用性。

        例如,假设要通过构造器编写一个带有集合的 Chooser 类和一个方法,并用该方法返回在集合中随机选择的一个元素 。 根据传给构造器的集合类型,可以用 chooser 充当游戏用的色子、魔术 8 球(一种卡片棋牌类游戏),或者一个蒙特卡罗模拟的数据源 。 下面是一个没有使用泛型的简单实现:

public class Chooser
{private final Object[] choiceArray;public Chooser(Collection choices) {choiceArray = choices.toArray();}public object choose() {Random rnd = ThreadLocalRandom.current();return choiceArray[rnd.nextInt(choiceArray.length)];}
}

        要使用这个类,必须将 choose 方法的返回值,从 Object 转换成每次调用该方法时想要的类型,如果搞错类型,转换就会在运行时失败 。努力将Chooser 修改成泛型,修改部分如下所示:

public class Chooser<T>
{private final T[] choiceArray;public Chooser(Collection<T> choices) {choiceArray = choices.toArray();}// choose method unchanged
}

        如果试着编译这个类,将会得到以下错误消息 :

        你可能会说 :这没什么大不了的,我可以把 Object 数组转换成 T 数组 : 

choiceArray = (T[]) choices.toArray() ;

        这样做的确消除了错误消息,但是现在得到了一条警告 :

        编译器告诉你,它无法在运行时检查转换的安全性,因为程序在运行时还不知道 T 是什么——记住,元素类型信息会在运行时从泛型中被擦除 。 这段程序可以运行吗?可以,但是编译器无法证明这一点 。 你可以亲自证明,只要将证据放在注释中,用一条注解禁止警告,但是最好能消除造成警告的根源 。

        要消除未受检的转换警告,必须选择用列表代替数组 。 下面是编译时没有出错或者警告的 Chooser 类版本 :

public class Chooser<T> {private final List<T> choiceList;public Chooser(Collection<T> choices) {choiceList = new ArrayList<> (choices);}public T choose() {Random rnd = ThreadLocalRandom.current();return choiceList.get(rnd.nextInt(choiceList.size());}
}

        这个版本的代码稍微冗长 一点,运行速度可能也会慢 一点, 但是在运行时不会得到ClassCastException 异常,为此也值了 。

        总而言之,数组和泛型有着截然不同的类型规则 。 数组是协变且可以具体化的;泛型是不可变的且可以被擦除的 。 因此,数组提供了运行时的类型安全,但是没有编译时的类型安全,反之,对于泛型也一样 。 一般来说,数组和泛型不能很好地混合使用 。 如果你发现自己将它们混合起来使用,并且得到了编译时错误或者警告,你的第一反应就应该是用列表代替数组 。

相关文章:

Effective Java笔记(28)列表优于数组

数组与泛型相比&#xff0c;有两个重要的不同点 。 首先&#xff0c;数组是协变的&#xff08; covariant &#xff09; 。 这个词听起来有点吓人&#xff0c;其实只是表示如果 Sub 为 Super 的子类型&#xff0c;那么数组类型 Sub[ ]就是Super[ ]的子类型。 相反&#xff0c;泛…...

做BI领域的ChatGPT,思迈特升级一站式ABI平台

8月8日&#xff0c;以「指标驱动 智能决策」为主题&#xff0c;2023 Smartbi V11系列新品发布会在广州丽思卡尔顿酒店开幕。 ​ 后疫情时代&#xff0c;BI发展趋势的观察与应对 在发布会上&#xff0c;思迈特CEO吴华夫在开场致辞中表示&#xff0c;当前大环境背景下&#xf…...

ELFK——ELK结合filebeat日志分析系统(2)

目录 一、filebeat 二、ELFK 1.原理简介 2.在ELK基础上部署filebeat 一、filebeat Filebeat&#xff0c;轻量级的开源日志文件数据搜集器。通常在需要采集数据的客户端安装 Filebeat&#xff0c;并指定目录与日志格式&#xff0c;Filebeat 就能快速收集数据&#xff0c;并…...

webSocket 协议是什么

webSocket 协议是什么&#xff0c;能简述一下吗&#xff1f; websocket 协议 HTML5 带来的新协议&#xff0c;相对于 http&#xff0c;它是一个持久连接的协议&#xff0c;它利用 http 协议完成握手&#xff0c;然后通过 TCP 连接通道发送消息&#xff0c;使用 websocket 协议可…...

CentOS 7迁移Anolis OS 8

背景&#xff1a;生产环境客户要求操作系统国产化 操作系统&#xff1a;Centos7.9 内核&#xff1a;5.4.108 服务器可以联网&#xff0c;进行在线迁移&#xff1a; # 下载迁移工具软件源 wget https://mirrors.openanolis.cn/anolis/migration/anolis-migration.repo -O /etc/y…...

Transformer 立体视觉 Depth Estimation

1. Intro 立体深度估计具有重要的意义,因为它能够重建三维信息。为此,在左右相机图像之间匹配相应的像素;对应像素位置的差异,即视差,可以用来推断深度并重建3D场景。最近基于深度学习的立体深度估计方法已经显示出有希望的结果,但仍然存在一些挑战。 其中一个挑战涉及使…...

vue去掉所有输入框两边空格,封装指令去空格,支持Vue2和Vue3,ElementUI Input去空格

需求背景 就是页面很多表单输入框&#xff0c;期望在提交的时候&#xff0c;都要把用户两边的空格去掉 ❌使用 vue 的指令 .trim 去掉空格 中间会输入不了空格&#xff0c; 比如我想输入 你好啊 中国, 这中间的空格输入不了&#xff0c;只能变成 你好啊中国 ❌在提交的时候使用…...

认识FFMPEG框架

FFMPEG全称: Fast Forward Moving Picture Experts Group (MPEG:动态图像专家组) ffmpeg相关网站: git://source.ffmpeg.org/ffmpeg.git http://git.videolan.org/?pffmpeg.git https://github.com/FFmpeg/FFmpeg FFMPEG框架基本组件: AVFormat , AVCodec, AVDevice, AVFil…...

Vue3 大屏数字滚动效果

父组件&#xff1a; <template> <div class"homePage"> <NumRoll v-for"(v, i) in numberList" :key"i" :number"v"></NumRoll> </div> </template> <script setup> import { onMounted, r…...

【深度学习注意力机制系列】—— SENet注意力机制(附pytorch实现)

深度学习中的注意力机制&#xff08;Attention Mechanism&#xff09;是一种模仿人类视觉和认知系统的方法&#xff0c;它允许神经网络在处理输入数据时集中注意力于相关的部分。通过引入注意力机制&#xff0c;神经网络能够自动地学习并选择性地关注输入中的重要信息&#xff…...

go 函数

go 语言函数 go 函数函数定义Go函数的特点如下函数作为参数可变参数相同类型可变参数不同类型可变参数 return语句作用概述空的return语句空白标识符多个返回值命名返回值 defer 语句作用引申出来的面试题for defer下面是一个使用defer的示例代码输出结果 匿名函数定义匿名函数…...

python之正则表达式

目录 正则表达式 python正则表达式方法 match search findall finditer compile 元字符匹配 元字符 量词 贪婪匹配和惰性匹配 正则表达式的group 语法 案例 正则表达式 正则表达式又称规则表达式&#xff0c;是使用单个字符串来描述、匹配某个句法规则的字符串…...

【LeetCode每日一题】——219.存在重复元素II

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 哈希表 二【题目难度】 简单 三【题目编号】 219.存在重复元素II 四【题目描述】 给你一个…...

篇六:适配器模式:让不兼容变兼容

篇六&#xff1a;“适配器模式&#xff1a;让不兼容变兼容” 开始本篇文章之前先推荐一个好用的学习工具&#xff0c;AIRIght&#xff0c;借助于AI助手工具&#xff0c;学习事半功倍。欢迎访问&#xff1a;http://airight.fun/ 另外有2本不错的关于设计模式的资料&#xff0c…...

【云原生】Docker-compose中所有模块学习

compose模块 模板文件是使用 Compose 的核心&#xff0c;涉及到的指令关键字也比较多。但大家不用担心&#xff0c;这里面大部分指令跟 docker run 相关参数的含义都是类似的。 默认的模板文件名称为 docker-compose.yml&#xff0c;格式为 YAML 格式。 version: "3&quo…...

广义积分练习

前置知识 无穷限积分瑕积分 练习 计算 ∫ 0 ∞ 1 x ( 1 x ) d x \int_0^{\infty}\dfrac{1}{\sqrt x(1x)}dx ∫0∞​x ​(1x)1​dx 解&#xff1a; x 0 \qquad x0 x0为瑕点 \qquad 原式 lim ⁡ a → 0 lim ⁡ b → ∞ ∫ a b 1 x ( 1 x ) d x lim ⁡ a → 0 lim ⁡…...

element-ui树形表格,左边勾选,右边显示选中的数据-功能(如动图)

功能如图 功能需求 表格树形表格勾选数据&#xff0c;右边显示对应勾选的数据内容&#xff0c;选中客户&#xff0c;自动勾选所有的店铺(子级)&#xff0c;选中其中一个店铺&#xff0c;自动勾选上客户(父级)&#xff0c;同时会存在只有客户&#xff08;下面没有子级的情况&am…...

Android数字价格变化的动画效果的简单实现

原理&#xff1a;使用ValueAnimator属性动画类实现&#xff0c;它通过值的改变手动设置对象的属性值来实现动画效果。直接贴代码&#xff1a; public static void doNumberAnim(TextView tvPrice, float startNumber, float endNumber) {ValueAnimator animator ValueAnimato…...

Win10无法投影关闭3D模式

Win10不小心开启了3D模式&#xff0c;插上投影仪就一闪一闪的&#xff0c;无法正投影 解决办法&#xff1a; 1. 打开注册表工具regedit&#xff0c;删除以下注册表&#xff0c;重启电脑 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\Configurat…...

FFmpeg 编码详细流程

介绍 FFmpeg的 libavcodec 模块完成音视频多媒体的编解码模块。FFmpeg 本身不具有音视频编码的功能和底层能力&#xff0c;只是对各类第三方的编码器API 进行封装调用。老版本的 FFmpeg 将avcodec_encode_video2()作为视频的解码函数 API&#xff0c;将avcodec_encode_audio2(…...

基于微信小程序的作业管理系统源码数据库文档

作业管理系统 摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和微信小程序来完成对系统的…...

ubuntu2404 gpu 没接显示器,如何保证远程显示的分辨率

1. 使用 xserver-xorg-video-dummy 创建虚拟显示器 如果系统在无物理显示器连接时无法识别显示输出&#xff0c;可以使用 xserver-xorg-video-dummy 驱动程序创建虚拟显示器。以下是设置步骤&#xff1a; 安装虚拟显示器驱动程序&#xff1a; sudo apt install xserver-xorg-v…...

深入理解JavaScript设计模式之闭包与高阶函数

目录 前言小序一场失败面试面试后的觉醒 闭包变量作用域&#xff1a;谁的地盘听谁的变量的生命周期&#xff1a;该走了&#xff0c;不该走的还在闭包的更多作用&#xff1a;不只是谈恋爱&#xff0c;还能干活&#xff01;1、封装私有变量&#xff1a;你的变量我来守护2、延长变…...

Ubuntu挂载本地镜像源(像CentOS 一样挂载本地镜像源)

1.挂载 ISO 镜像 sudo mount -o loop /ubuntu-22.04.5-desktop-amd64.iso /mnt/iso 2.备份现有的软件源配置文件&#xff1a; sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 3.编辑软件源配置文件 编辑 /etc/apt/sources.list sudo nano /etc/apt/sources.l…...

【汇编逆向系列】三、函数调用包含单个参数之float类型-xmm0寄存器,sub,rep,stos,movss,mulss,addss指令

一、汇编代码 single_float_param:0000000000000060: F3 0F 11 44 24 08 movss dword ptr [rsp8],xmm00000000000000066: 57 push rdi0000000000000067: 48 83 EC 10 sub rsp,10h000000000000006B: 48 8B FC mov …...

分布式锁实战:Redisson vs. Redis 原生指令的性能对比

分布式锁实战&#xff1a;Redisson vs. Redis 原生指令的性能对比 引言 在DIY主题模板系统中&#xff0c;用户可自定义聊天室的背景、图标、动画等元素。当多个运营人员或用户同时修改同一模板时&#xff0c;若没有锁机制&#xff0c;可能出现“甲修改了背景色&#xff0c;乙…...

【C/C++】std::vector成员函数清单

文章目录 std::vector使用指南1 不同版本提供的能力基础&#xff1a;C98 / C03 提供的成员函数C11 新增的成员函数C14&#xff1a;基本无变化&#xff08;主要是标准库泛化&#xff0c;非 vector 成员变化&#xff09;C17 引入的新特性&#xff08;间接影响&#xff09;C20 新增…...

MS8551/MS8552/MS8554 单电源、轨到轨输入输出、高精度运放,可替代AD8551/AD8552/AD8554

MS8551/MS8552/MS8554 单电源、轨到轨输入输出、高精度运放&#xff0c;可替代AD8551/AD8552/AD8554 简述 MS8551/8552/8554 是轨到轨输入输出的高精度运算放大器&#xff0c;它有极低的输入失调电压和偏置电流&#xff0c;单电源电压范围为 1.8V 到 5V 。 MS8551/8552/85…...

12-Oracle 23ai Vector 使用ONNX模型生成向量嵌入

一、Oracle 23ai Vector Embeddings 核心概念​ 向量嵌入&#xff08;Vector Embeddings&#xff09;​​ -- 将非结构化数据&#xff08;文本/图像&#xff09;转换为数值向量 - - 捕获数据的语义含义而非原始内容 - 示例&#xff1a;"数据库" → [0.24, -0.78, 0.5…...

榕壹云健身预约系统:多门店管理的数字化解决方案(ThinkPHP+MySQL+UniApp实现)

随着全民健身热潮的兴起&#xff0c;传统健身房在会员管理、课程预约、多门店运营等方面面临诸多挑战。针对这一需求&#xff0c;我们开发了一款基于ThinkPHPMySQLUniApp的榕壹云健身预约系统&#xff0c;为中小型健身机构及连锁品牌提供高效、灵活的数字化管理工具。本文将详细…...