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.rhkb.cn/news/81766.html

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

相关文章

linux 安装go 1.18版本

首先去官网找到对应的版本 直接下载下来&#xff08;如果服务器可以直接访问到go 官网也可以wget直接下载到服务器&#xff09; 然后把该包上传到linux 的/usr/local 目录下 然后直接解压安装该包&#xff1a; sudo tar -C /usr/local -zxvf go1.18.10.linux-amd64.tar.gz 然…

Vscode-工具使用

Vscode &#xff0c;这玩意儿是开源的&#xff0c;以前用收费的破解版&#xff0c;过段时间就高版本不匹配&#xff0c;这次搞个不要钱的玩玩&#xff0c;记录使用心得 下载 下载地址&#xff1a;官网 点击下载&#xff0c;但是这里有个问题下载比较慢&#xff0c;解决办法&a…

Dockerfile部署golang,docker-compose

使用go镜像打包&#xff0c;运行在容器内 redis和mysql用外部的 项目目录结构 w1go项目&#xff1a; Dockerfile # 这种方式是docker项目加上 本地的mysql和redis环境 # go打包的容器 FROM golang:alpine AS builder# 为我们镜像设置一些必要的环境变量 ENV GO111MODULEon …

Vue3 第五节 一些组合式API和其他改变

1.provide和inject 2.响应式数据判断 3.Composition API的优势 4.新的组件 5.其他改变 一.provide和inject 作用&#xff1a;实现祖与后代组件间通信 套路&#xff1a;父组件有一个provide选项来提供数据&#xff0c;后代组件有一个inject选项来开始使用这些数据 &…

uniapp 微信小程序 上下滚动的公告通知(只取前3条)

效果图&#xff1a; <template><view class"notice" click"policyInformation"><view class"notice-icon"><image mode"aspectFit" class"img" src"/static/img/megaphone.png"></i…

具有吸引子的非线性系统(MatlabSimulink实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

BIO,NIO,AIO总结

文章目录 1. BIO (Blocking I/O)1.1 传统 BIO1.2 伪异步 IO1.3 代码示例 1.4 总结2. NIO (New I/O)2.1 NIO 简介2.2 NIO的特性/NIO与IO区别1)Non-blocking IO&#xff08;非阻塞IO&#xff09;2)Buffer(缓冲区)3)Channel (通道)4)Selector (选择器) 2.3 NIO 读数据和写数据方式…

刚刚更新win11,记事本消失怎么处理?你需要注意些什么?

记录window11的bug hello&#xff0c;我是小索奇 昨天索奇从window10更新到了window11&#xff0c;由于版本不兼容卸载了虚拟机&#xff0c;这是第一个令脑壳大的&#xff0c;算了&#xff0c;还是更新吧&#xff0c;了解了解win11的生态&#xff0c;后期重新装虚拟机 第一个可…

TCP三次握手四次断开

一、了解TCP &#x1f345;TCP &#xff1a;传输控制协议&#xff0c;是一种面向连接的可靠的传输协议。 什么是可靠的传输协议&#xff1f;如何保障可靠传输&#xff1f; 保证可靠性&#xff1a; 1.确认机制 2.重传输机制什么是面向连接&#xff1f;如何保障面…

教雅川学缠论07-中枢实战众泰汽车000980

本文实战众泰汽车 下面是2023年11月14-2023年8月8众泰汽车日K图 先画日K 接下来处理包含&#xff0c;就变成下面这个样子 下面在套上缠论的理论&#xff0c;未来股价的走势应该是红色椭圆形虚线里面的样子 好了&#xff0c;文章就到这里&#xff0c;如果众泰最终不是这个走势…

人工智能与物理学(软体机器人能量角度)的结合思考

前言 好久没有更新我的CSDN博客了&#xff0c;细细数下来已经有了16个月。在本科时期我主要研究嵌入式&#xff0c;研究生阶段对人工智能感兴趣&#xff0c;看了一些这方面的论文和视频&#xff0c;因此用博客记录了一下&#xff0c;后来因为要搞自己的研究方向&#xff0c;就…

Maven入职学习

一、什么是Maven&#xff1f; 概念&#xff1a; Maven是一种框架。它可以用作依赖管理工具、构建工具。 它可以管理jar包的规模、jar包的来源、jar包之间的依赖关系。 它的用途就是管理规模庞大的jar包&#xff0c;脱离IDE环境执行构建操作。 具体使用&#xff1a; 工作机…

【MATLAB第67期】# 源码分享 | 基于MATLAB的morris全局敏感性分析

【MATLAB第67期】# 源码分享 | 基于MATLAB的morris全局敏感性分析 一、代码展示 clear all npoint100;%在分位数超空间中要采样的点数(计算次数iternpoint*(nfac1) nfac20;%研究函数的不确定因素数量 [mu, order] morris_sa1((x)test_function(x), nfac, npoint)for t1:size…

IELTS图表类作文基础知识

表格可以用table或chart来表示。 其实&#xff0c;数据类图表除了可以用chart表示&#xff0c;也可以用其他单词。 表格又可以称为table。而带有几何图形的图表可以用graph来表示。 像饼状图、折线图、柱状图这样用几何图形&#xff0c;或者直接用表格来呈现数据的形式&#x…

【Linux升级之路】5_基础IO

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux升级之路】 ✒️✒️本篇内容&#xff1a;文件操作&#xff0c;文件管理&#xff0c;重定向&#xff0c;简易shell添加重定向功能&#xff0c;文件属…

如何在风控引擎中快速增加策略

风控策略是由规则和模型组成的、用以实现风险控制目标的集合。模型与规则相辅相成&#xff0c;相互补充&#xff0c;在不同的业务阶段彼此的侧重点不同。新业务上线期&#xff0c;数据量不足&#xff0c;策略以规则为主&#xff1b;当业务运行一段时候后&#xff0c;数据有了一…

【JavaEE进阶】Spring创建与使用

文章目录 一. 创建 Spring 项目1.1 创建一个Maven项目1.2 添加Spring依赖1.4. 创建一个启动类 二. 将 Bean 对象存放至 Spring 容器中三. 从 Spring 容器中读取到 Bean1. 得到Spring对象2. 通过Spring 对象getBean方法获取到 Bean对象【DI操作】 一. 创建 Spring 项目 接下来使…

如果你需要使用重试机制,请使用Spring官方的Spring Retry

Spring Retry 是 Spring Framework 中的一个模块&#xff0c;提供了一种简单的方式来在应用程序中实现重试机制。 在应用程序中&#xff0c;如果遇到了一些不可避免的错误&#xff0c;比如网络连接失败、数据库连接失败等&#xff0c;我们通常需要对这些错误进行重试&#xff…

excel 方框内打钩符号

大写字母 R &#xff0c;字体选择 Wingdings 2

[mongo]应用场景及选型

应用场景及选型 MongoDB 数据库定位 OLTP 数据库横向扩展能力&#xff0c;数据量或并发量增加时候架构可以自动扩展灵活模型&#xff0c;适合迭代开发&#xff0c;数据模型多变场景JSON 数据结构&#xff0c;适合微服务/REST API基于功能选择 MongoDB 关系型数据库迁移 从基…