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 异常,为此也值了 。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/33105.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无涯教程-Perl - link函数

描述 此函数创建一个新文件名NEWFILE,链接到文件OLDFILE。该函数创建一个硬链接&#xff1b;如果需要符号链接,请使用符号链接功能。 语法 以下是此函数的简单语法- link OLDFILE,NEWFILE返回值 如果失败,此函数返回0,如果成功,则返回1。 例 以下是显示其基本用法的示例…

开发一个RISC-V上的操作系统(六)—— 中断(interrupt)和异常(exception)

目录 往期文章传送门 一、控制流 &#xff08;Control Flow&#xff09;和 Trap 二、Exceptions, Traps, and Interrupts Contained Trap Requested Trap Invisible Trap Fatal Trap 异常和中断的异同 三、RISC-V的异常处理 mtvec&#xff08;Machine Trap-Vector Ba…

从零学算法154

154.已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,4,4,5,6,7] 在变化后可能得到&#xff1a; 若旋转 4 次&#xff0c;则可以得到 [4,5,6,7,0,1,4] 若旋转 7 次&#…

前端必学的CSS3波浪效果演示

以下是这三种CSS3波浪效果的使用说明 使用translateX和translateZ属性创建波浪效果&#xff1a; 使用场景&#xff1a; 适用于需要在X轴上平移和在Z轴上应用3D变换的波浪效果。可以用于创建具有起伏效果的海浪、水面波纹等效果。 优点&#xff1a; 通过3D变换&#xff0c;…

内生安全构建数据存储

一、数据安全成为防护核心&#xff0c;存储安全防护不容有失 1、数据作为企业的核心资产亟需重点保护&#xff0c;数据安全已成网络空间防护核心 2、国家高度重视关键信息基础设施的数据安全&#xff0c;存储安全已成为审核重点 二、存储安全是数据安全的关键一环&#xff0c;应…

AIGC技术揭秘:探索火热背后的原因与案例

文章目录 什么是AIGC技术&#xff1f;为何AIGC技术如此火热&#xff1f;1. 提高效率与创造力的完美结合2. 拓展应用领域&#xff0c;创造商业价值3. 推动技术创新和发展 AIGC技术案例解析1. 艺术创作&#xff1a;生成独特的艺术作品2. 内容创作&#xff1a;实时生成各类内容3. …

SolidWorks不能使用选择如允许此选择将生成有冲突的前后关系

SolidWorks不能使用选择如允许此选择将生成有冲突的前后关系 1 SolidWorks不能使用选择如允许此选择将生成有冲突的前后关系 1 SolidWorks不能使用选择如允许此选择将生成有冲突的前后关系 https://www.swrjzxw.com/1556.html SolidWorks装配体时 显示 不能使用选择如允许此选…

哪些CRM的报价公开且透明?

企业在选型时&#xff0c;会发现很多品牌的CRM系统价格并不透明&#xff0c;往往都是需要跟产品顾问沟通后才能了解。下面推荐一款价格实在的CRM系统&#xff0c;所有报价公开透明&#xff0c;那就是Zoho CRM。 Zoho CRM是什么&#xff1f; Zoho CRM是一款在线CRM软件&#x…

NAS相关

Debian11 更换软件源 备份 #备份软件源列表 cp /etc/apt/sources.list /etc/apt/sources.list.bak编辑sources.list nano /etc/apt/sources.list替换文件内容 deb http://mirrors.163.com/debian/ bullseye main non-free contrib deb http://mirrors.163.com/debian/ bull…

SAP BAPI 创建/修改MD61/MD62计划独立需求预测

MD61 创建&#xff1a; BAPI: BAPI_REQUIREMENTS_CREATE CLEAR: lv_error,ls_requirements_item,lt_requirements_schedule_in,ls_requirements_schedule_in,lt_return_n,ls_return_n,lv_reqmtsplannumber."工厂ls_requirements_item-plant lv_werks."MRP AR…

pytorch模型加载caffe模型的权重

一、将caffe模型的权重转成dict格式 caffe库的编译可以参考我之前写的一篇博客&#xff1a;ImportError: dynamic module does not define module export function (PyInit__caffe)问题解决记录_chen_zn95的博客-CSDN博客 安装好后使用以下脚本便可将caffe模型的参数名和参数…

分布式测试插件 pytest-xdist 使用详解

目录 使用背景&#xff1a; 使用前提&#xff1a; 使用快速入门&#xff1a; 使用小结&#xff1a; 使用背景&#xff1a; 大型测试套件&#xff1a;当你的测试套件非常庞大&#xff0c;包含了大量的测试用例时&#xff0c;pytest-xdist可以通过并行执行来加速整体的测试过…

js中的break和continue中的区别

js中break和continue有着一些差别。 首先&#xff0c;虽然break和continue都有跳出循环的作用&#xff0c;但break是完全跳出循环&#xff0c;而continue则是跳出一次循环&#xff0c;然后开启下一次的循环。 下面我就来举几个例子吧。 var num 0;for(var i 1;i < 10;i){i…

如何使用ChatGPT设计LOGO,只需知道品牌名字就能完成傻瓜式操作

​独特且引人注目的LOGO对于引导用户/消费者快速识别并与你建立联系至关重要。然而&#xff0c;聘请专业的设计师来创建个性化LOGO可能非常昂贵。这里可以使用使用ChatGPT。[1] 你只需要&#xff1a; 准备好公司名称&#xff1b; 能用ChatGPT&#xff0c;用来给BingChat喂log…

学习总结(TAT)

好久都没交总结了&#xff0c;今天把之前的思路和错误整理了一下&#xff1a; 在服务器和客户端两侧&#xff0c;不可以同时先初始化获取输入流&#xff0c;否则会造成堵塞&#xff0c;同时为这位作者大大打call&#xff1a; (3条消息) 关于Java Socket和创建输入输出流的几点…

一、安全世界观

文章目录 1、 Web安全简史1.1 中国黑客简史1.2 黑客技术的发展历程1.3 web安全的兴起 2、黑帽子、白帽子3、安全的本质4、安全三要素5、如何实施安全评估5.1 资产等级划分5.2 威胁分析5.3 风险分析5.4 设计安全方案 6、白帽子兵法6.1 Secure By Default6.2 纵深防御原则6.3 数据…

java的junit之异常测试、参数化测试、超时测试

1.对可能抛出的异常进行测试 异常本身是方法签名的一部分测试错误的输入是否导致特定的异常 summary 测试异常可以使用Test(expectedExceptio.class)对可能发生的每种类型的异常进行测试 2.参数化测试 如果待测试的输入和输出是一组数据&#xff1a; 可以把测试数据组织起…

Oracle时间查询使用笔记:sysdate用法

Oracle的sysdate用法 通常会有 sysdate - 1 / 12这种&#xff0c;或者sysdate - 1 / 24/3 这两种用法,表示从当前时间往前推若干时间 下面就用sysdate - A/B,sysdate - A/B/C代替 第一种 sysdate - A/B型&#xff0c;这种结果是小时&#xff0c;A代表天数&#xff0c;B代表小时…

学习51单片机怎么开始?

学习的过程不总是先打好基础&#xff0c;然后再盖上层建筑&#xff0c;尤其是实践性的、工程性很强的东西。如果你一定要先全面打好基础&#xff0c;再学习单片机&#xff0c;我觉得你一定学不好&#xff0c;因为你的基础永远打不好&#xff0c;因为基础太庞大了&#xff0c;基…

Spring AOP 切点表达式

参考博客&#xff1a; 参考博客