Effective Java笔记(26)请不要使用原生态类型

news2024/12/28 23:54:43

        首先介绍一些术语 。 声明中具有一个或者多个类型参数( type parameter )的类或者接口,就是泛型( generic )类或者接口 。 例如,List 接口就只有单个类型参数 E ,表示列表的元素类型 。这个接口的全称是 List<E> (读作“ E 的列表”),但是人们经常把它简称为 List 。 泛型类和接口统称为泛型( generic type ) 。

        每一种泛型定义一组参数化的类型( parameterized type),构成格式为 : 先是类或者接口的名称,接着用尖括 号(<>) 把对应于泛型形式类型参数的实际类型参数列表括起来 。 例如,List< String> (读作 “字符串列表”)是一个参数化的类型,表示元素类型为 String 的列表 。(String 是与形式的类型参数 E 相对应的实际类型参数 。)

        最后一点,每一种泛型都定义一个原生态类型( raw type ),即不带任何实际类型参数的泛型名称 。 例如,与 List < E>相对应的原生态类型是 List 。 原生态类型就像从类型声明中删除了所有泛型信息一样 。 它们的存在主要是为了与泛型出现之前的代码相兼容 。

        在 Java 增加泛型之前,下面这个集合声明是值得参考的 。 从 Java 9 开始,它依然合法,但是已经没什么参考价值了:

private final Collection stamps = ...;

        如果现在使用这条声明,并且不小心将一个 coin 放进了 stamp 集合中,这一错误的插入照样得以编译和运行,不会出错(不过编译器确实会发出一条模糊 的警告信息) :

stamps.add(new Coin( ... ));

直到从 stamp 集合中获取 co 工 n 时才会收到一条错误提示:

for (Iterator i = stamps.iterator(); i.hasNext(); )
    Stamp stamp = (Stamp) i.next(); 
        stamp.cancel() ;

        出错之后应该尽快发现, 最好是编译时就发现。在本例中,直到运行时才发现错误,已经出错很久了,而且它在代码中所处的位置,距离包含错误的这部分代码已经很远了 。一旦发现ClassCastException ,就必须搜索代码,查找将coin 放进 stamp 集合的方法调用 。 此时编译器帮不上忙,因为它无法理解这种注释 :“ Contains only Stamp instances ”(只包含 Stamp 实例)。

有了泛型之后,类型声 明中可以包含以下信息,而不是注释 :

private final Collection<Stamp> stamps = ...;

        通过这条声明,编译器知道 stamps 应该只包含 Stamp 实例,并给予保证( guarantee),假设整个代码库在编译过程中都没有发出任何警告 。当 stamps利用一个参数化的类型进行声明时,错误的插入会产生一条编译时的错误消息,告诉你具体是哪里出错了。

        从集合中检索元素时,编译器会替你插入隐式的转换,并确保它们不会失败(依然假设所有代码都没有产生或者隐瞒任何编译警告) 。 假设不小心将 coin 插入 stamp 集合,这显得有点牵强,但这类问题却是真实的 。 例如,很容易想象有人会不小心将一个Biginteger 实例放进一个原本只包含 BigDecimal 实例的集合中 。

        如上所述,使用原生态类型(没有类型参数的泛型)是合法的,但是永远不应该这么做 。 如果使用原生态类型,就失掉了泛型在安全性和描述性方面的所有优势 。 既然不应该使用原生态类型 ,为什么 Java 语言的设计者还要允许使用它们呢?这是为了提供兼容性 。

        因为泛型出现的时候,Java 平台即将进入它的第二个十年,已经存在大量没有使用泛型的Java 代码 。 人们认为让所有这些代码保持合法,并且能够与使用泛型的新代码互用,这一点很重要。它必须合法才能将参数化类型的实例传递给那些被设计成使用普通类型的方法,反之亦然 。 这种需求被称作移植兼容性( Migration Compatibility ),促成了支持原生态类型,以及利用擦除( erasure) 实现泛型的决定 。

        虽然不应该在新代码中使用像 List 这样的原生态类型,使用参数化的类型以允许插入任意对象(比如 List<Object >)是可行的 。 原生态类型 List 和参数化的类型 List<Object>之间到底有什么区别呢?不严格地说,前者逃避了泛型检查,后者则明确告知编译器,它能够持有任意类型的对象。 虽然可以将 List<String> 传递给类型 List 的参数,但是不能将它传给类型 List<Object>的参数。 泛型有子类型化( subtyping )的规则,List<String>是原生态类型 List 的一个子类型,而不是参数化类型 List<Object>的子类型 。因此, 如果使用像 List 这样的原生态类型,就会失掉类型安全性 , 但是如果使用像 List<Object >这样的参数化类型,则不会

        为了更具体地进行说明,请参考下面的程序:

public static void main(String[] args) {
    List<String> strings = new ArrayList<>();
    unsafeAdd(strings, Integer.value0f(42));
    String s = strings.get(0); // Has compiler-generated cast

private static void unsafeAdd(List list, object o) {
    list.add(o);
}

        这段程序可以进行编译,但是因为它使用了原生态类型 List ,你会收到一条警告:

        实际上,如果运行这段程序,在程序试图将 strings.get(o)的调用结果 Integer转换成 String时,你会收到一个 ClassCastException 异常 。 这是一个编译器生成的转换,因此一般保证会成功,但是我们在这个例子中忽略了一条编译器警告,为此付出了代价 。如果在 unsafeAdd 声明中用参数化类型 List<Object>代替原生态类型 List ,并试着重新编译这段程序,会发现它无法再进行编译了,并发出以下错误消息:

        在不确定或者不在乎集合中的元素类型的情况下,你也许会使用原生态类型 。 例如,假设想要编写一个方法,它有两个集合,并从中返回它们共有元素的数量 。 如果你对泛型还不熟悉,可以参考以下方式来编写这种方法:

static int numElementsInCommon(Set s1, Set s2) {
    int result = 0;
    for (Object o1 : s1)
        if (s2.contains(o1))
            result++;
    return result;
}

        这个方法可行,但它使用了原生态类型,这是很危险的 。 安全的替代做法是使用无限制的通配符类型( unbounded wildcard type ) 。 如果要使用泛型,但不确定或者不关心实际的类型参数,就可以用一个问号代替 。 例如,泛型 Set<E>的无限制通配符类型为 Set <?>(读作“某个类型的集合”) 。 这是最普通的参数化 S et 类型,可 以持有任何集合 。 下面是numElementsinCommon 方法使用了无限制通配符类型时的情形 :

static int numElementsInCommon(Set<?> s1, Set<?> s2) {...}

        无限制通配类型 Set <>和原生态类型 Set 之间有什么区别呢?这个问号真正起到作用了吗?这一点不需要赘述,但通配符类型是安全的,原生态类型则不安全 。 由于可以将任何元素放进使用原生态类型的集合中,因此很容易破坏该集合的类型约束条件(如之前范例中所示的 unsafeAdd 方法);但不能将任何元素(除了 null 之 外)放到 Collection < ?>中 。 如果尝试这么做,将会产生一条像这样的编译时错误消息:

        这样的错误消息显然无法令人满意,但是编译器已经尽到了它的职责,防止你破坏集合的类型约束条件 。 你不仅无法将任何元素(除了 null 之外)放进 Collection <?> 中,而且根本无法猜测你会得到哪种类型的对象 。 要是无法接受这些限制,就可以使用泛型方法或者有限制的通配符类型 。

        不要使用原生态类型,这条规则有几个小小的例外 。 必须在类文字中 使用原生态类型 。 规范不允许使用参数化类型(虽然允许数组类型和基本类型)。换句话说, List.class 、 String[].class 和 int.class 都合法,但是 List<String.class 和 List<?>.class 则不合法 。

        这条规则的第二个例外与 ins ta口 ceof 操作符有关 。 由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配柯:类型上使用 instanceof 操作符是非法的 。 用无限制通配符类型代替原生态类型,对 instanceof 操作符的行为不会产生任何影响 。 在这种情况下,尖括号( <>)和问号(?)就显得多余了 。 下面是利用泛型来使用 instanceof操作符的首选方法

if (o instanceof Set) {
    Set<?> s = (Set<?>) o;
}

        注意,一旦确定这个 o 是个 Set ,就必须将它转换成通配符类型 Set <?>,而不是转换成原生态类型 Set 。 这是个受检的( checked )转换,因此不会导致编译时警告 。

        总而言之,使用原生态类型会在运行时导致异常,因此不要使用 。 原生态类型只是为了与引人泛型之前的遗留代码进行兼容和互用而提供的 。 让我们做个快速的回顾:Set<Object >是个参数化类型,表示可以包含任何对象类型的一个集合;Set <?>则是一个通配符类型,表示只能包含某种未知对象类型的一个集合;Set 是一个原生态类型,它脱离了泛型系统 。 前两种是安全的,最后一种不安全。

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

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

相关文章

码云 Gitee + Jenkins 配置教程

安装jdk 安装maven 安装Jenkins https://blog.csdn.net/minihuabei/article/details/132151292?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132151292%22%2C%22source%22%3A%22minihuabei%22%7D 插件安装 前往 Manage Jen…

聊聊汽车电子的话题

当谈到汽车电子时&#xff0c;有许多有趣的话题可以探讨。以下是一些可能感兴趣的话题&#xff1a; 自动驾驶技术&#xff1a;自动驾驶技术正变得越来越先进&#xff0c;它们如何在汽车中实现&#xff1f;它们将如何改变我们的交通方式以及对道路安全的影响&#xff1f; 电动汽…

【LeetCode每日一题】——85.最大矩形

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 矩阵 二【题目难度】 困难 三【题目编号】 85.最大矩形 四【题目描述】 给定一个仅包含 0 …

【Java split】split() 函数分割空字符串后数组长度为1的原因以及规避措施(105)

问题现象: import java.util.ArrayList; import java.util.Arrays; import java.util.List;public class test06 {public static void main(String[] args) {// Java split()函数 分割空字符串长度为1的解释&#xff1b;String s2 "";String[] arr2 s2.split(&quo…

[SWPUCTF 2022 新生赛]numgame

这道题有点东西网页一段计算框&#xff0c;只有加和减数字&#xff0c;但是永远到大不了20&#xff0c;页面也没啥特别的&#xff0c;准备看源码&#xff0c;但是打不开&#xff0c;我以为是环境坏掉了&#xff0c;看wp别人也这样&#xff0c;只不过大佬的开发者工具可以打开&a…

elementUi select下拉框触底加载异步分页数据

在Element UI中&#xff0c;可以通过监听select下拉框的visible-change事件来实现触底加载下一页的效果。 方式一&#xff1a;利用elementUi的事件 具体步骤如下&#xff1a; 首先&#xff0c;在select组件中设置&#xff1a;visible-change"handleVisibleChange"…

Unity之获取用户地理位置

1.直接利用三方API获取&#xff1a; 1.1 利用bilibili的api 【未知稳定性】 public void Awake() {StartCoroutine(GetLocationInfoNew());}/// <summary>/// 利用bilibili的接口通过ip直接获取城市信息/// </summary>IEnumerator GetLocationInfoNew() {//UnityW…

在 Linux 上以 All-in-One 模式安装 KubeSphere

官方文档&#xff1a;https://www.kubesphere.io/zh/docs/v3.3/quick-start/all-in-one-on-linux/ 操作系统 最低配置 Ubuntu&#xff1a; 16.04,18.04, 20.04, 22.04 2 核 CPU&#xff0c;4 GB 内存&#xff0c;40 GB 磁盘空间Debian Buste&#xff1a;Stretch 2 核 CPU&am…

Leetcode周赛 | 2023-8-5

2023-8-5 题1体会我的代码 题2体会我的代码 题1 体会 一开始是觉得这道题是贪心的&#xff0c;选出现次数最多的元素&#xff0c;但是发现&#xff0c;当有多个元素出现次数均为最多时&#xff0c;似乎很难处理&#xff0c;就放弃了。转而问ChatGPT &#xff0c;结果让自己走上…

可视化高级绘图技巧100篇-总论

前言 优秀的数据可视化作品可以用三个关键词概括&#xff1a;准确、清晰、优雅。 准确&#xff1a;精准地反馈数据的特征信息&#xff08;既不遗漏也不冗余&#xff0c;不造成读者疏漏&误读细节&#xff09; 清晰&#xff1a;获取图表特征信息的时间越短越好 优雅&…

吃瓜教程-Task05

目录 支持向量机 间隔与支持向量 SVM基本型 对偶问题 kkt条件 例子 对偶问题 例子 对偶问题原理解释 软间隔与正则化 替代损失函数 支持向量回归 例子 支持向量机 间隔与支持向量 在样本空间中&#xff0c;划分超平面可通过如下线性方程来描述: 样本空间中任意点x到…

Doris(四)-聚合模型的使用

pre 前言 这里使用聚合模型&#xff0c;可以在导入数据的时候&#xff0c;就将部分数据做预处理&#xff0c;提高查询效率。 同样&#xff0c;因为是预处理&#xff0c;因此&#xff0c;数据细节会丢失。 1, 建表语句 create table if not exists user_landing_record_new …

基于Java+SpringBoot+Vue的篮球竞赛预约平台设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

框框大学之——教育技术学

清一色劝退的教育技术学。。。。。。 https://www.kkdaxue.com/?current1&major%E6%95%99%E8%82%B2%E6%8A%80%E6%9C%AF%E5%AD%A6&pageSize10&sortFieldcreateTime&sortOrderdescend 总结&#xff1a; 1 杂而不经 2 摆烂劝退居多 3 适合躺平 4 考公不行 5 要多…

探秘手机隐藏的望远镜功能:开启后,观察任何你想看的地方

当今的智能手机不仅仅是通信工具&#xff0c;它们蕴藏着各种隐藏的功能&#xff0c;其中之一就是让你拥有望远镜般的观察能力。是的&#xff0c;你没有听错&#xff01;今天我们将探秘手机中隐藏的望远镜功能&#xff0c;这项神奇的功能可以让你打开后&#xff0c;轻松观察任何…

L2CS-Net: 3D gaze estimation

L2CS-Net: Fine-Grained Gaze Estimation in Unconstrained Environments论文解析 摘要1. 简介2. Related Work3. METHOD3.1 Proposed loss function3.2 L2CS-Net 结构3.3 数据集3.4 评价指标 4. 实验4.1 实验结果 论文地址&#xff1a;L2CS-Net: Fine-Grained Gaze Estimation…

R语言安装包Seurat

环境Ubuntu22&#xff0c;R4.1 also installing the dependencies ‘curl’, ‘openssl’, ‘httr’, ‘plotly’ R包安装的时候报了这个错误ERROR: dependencies httr, plotly are not available for package Seurat 解决方法&#xff0c;退出R&#xff0c;在terminal中键入…

将整数,结构体,结构体数组,链表写到文件

在之前的学习中&#xff0c;忘文件中写的内容都是字符串或字符&#xff0c;本节学习如何写入其他各种类型的数据。 回看write和read函数的形式&#xff1a; ssize_t write(int fd, const void *buf, size_t count); ssize_t read(int fd, void *buf, size_t count); 其中&a…

(论文复现)DeepAnt模型复现及应用

DeepAnt论文如下&#xff0c;其主要是用于时间序列的无监督粗差探测。 其提出的模型架构如下&#xff1a; 该文提出了一个无监督的时间序列粗差探测模型&#xff0c;其主要有预测模块和探测模块组成&#xff0c;其中预测模块的网络结构如下。 预测结构是将时间序列数据组…

mfc100u.dll丢失的多种解决方法分享,最新的修复mfc100u.dll方案

mfc100u.dll丢失可以说是见怪不怪的了&#xff0c;只要经常使用电脑的人&#xff0c;一般都会碰到一两次这种dll文件缺失的情况&#xff0c;今天主要是来给大家讲解一下mfc100u.dll丢失的多种解决方法&#xff0c;让你以后遇到这情况再也不需要头大。 一.mfc100u.dll为啥会丢失…