Effective Java笔记(29)优先考虑泛型

        一般来说 ,将集合声 明参数化,以及使用 JDK 所提供的泛型方法,这些都不太困难 。编写自己的泛型会比较困难一些,但是值得花些时间去学习如何编写 。

        以简单的(玩具)堆校实现为例 :

// Object -based collection - a prime candidate for generics
public class Stack
{
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(0bject e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }
    public boolean isEmpty () {
        return size == 0;
    }
    private void ensureCapacity () {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

        这个类应该先被参数化,但是它没有,我们可以在后面将它泛型化( generify ) 。 换句话说,可以将它参数化,而又不破坏原来非参数化版本的客户端代码 。 也就是说,客户端必须转换从堆楼里弹出的对象,以及可能在运行时失败的那些转换 。 将类泛型化的第一步是在它的声明中添加一个或者多个类型参数 。 在这个例子中有一个类型参数,它表示堆桔的元素类型,这个参数的名称通常为 E 。

        下一步是用相应的类型参数替换所有的 Object 类型,然后试着编译最终的程序:

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    public Stack() {
        elements = new E[DEFAULT_INITIAL_CAPACITY] ;
    }
    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }
    public E pop( {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }
    ...// no changes in isEmpty or ensureCapacity
}

        通常,你将至少得到 一个错误提示或警告,这个类也不例外 。 幸运的是,这个类只产生一个错误,内容如下:

        你不能创建不可具体化的( non-reifiable )类型的数组,如 E 。 每当编写用数组支持的泛型时,都会出现这个问题 。 解决这个问题有两种方法 。 第一种,直接绕过创建泛型数组的禁令 : 创建一个 Object 的数组,并将它转换成泛型数组类型 。 现在错误是消除了,但是编译器会产生一条警告 。 这种用法是合法的,但(整体上而言)不是类型安全的:

        编译器不可能证明你的程序是类型安全的,但是你可以 。 你自己必须确保未受检的转换不会危及程序的类型安全性 。 相关的数组(即 elements 变量)保存在一个私有的域中,永远不会被返回到客户端,或者传给任何其他方法 。 这个数组中保存的唯一元素,是传给push 方法的那些元素,它们的类型为 E ,因此未受检的转换不会有任何危害 。 

        一旦你证明了未受检的转换是安全的,就要在尽可能小的 范围中禁止警告 。 在这种情况下,构造器只包含未受检的数组创建,因此可以在整个构造器中禁止这条警告 。 通过增加一条注解   @SuppressWarnings 来完成禁止,Stack 能够正确无误地进行编译,你就可以使用它了,无须显式的转换,也无须担心会出现 ClassCastException 异常:

@SuppressWarnings ("unchecked")
public Stack() {
    elements = (E[]) new Object [DEFAULT_INITIAL_CAPACITY];
}

         消除 Stack 中泛型数组创建错误的第 二种方法是,将 elements 域的类型从 E []改为 Object[] 。 这么做会得到一条不同的错误:

         通过把从数组中获取到的元素由 Object 转换成 E ,可以将这条错误变成一条警告:

        由于 E 是一个不可具体化的( non -reifiable )类型,编译器无法在运行时检验转换 。 你还是可以自己证实未受检的转换是安全的,因此可以禁止该警告 。 我们只要在包含未受检转换的任务上禁止警告,而不是在整个 pop 方法上禁止就可以了,方法如下:

public E pop() {
    if(size==0)
        throw new EmptyStackException();
    // push requires elements to be of type E, so cast is correct
    @SuppressWarnings ("unchecked") E result =
        (E) elements[--size];
    elements[size] = null; // Eliminate obsolete reference
    return result;
}

        这两种消除泛型数组创建的方法,各有所长 。 第一种方法的可读性更强 : 数组被声明为 E []类型清楚地表明它只包含 E 实例 。 它也更加简洁 : 在一个典型的泛型类 中,可以 在代码中的多个地方读取到该数组;第一种方法只需要转换一次(创建数组的时候),而第二种方法则是每次读取一个数组元素时都需要转换一次 。 因此,第一种方法优先,在实践中也更常用 。 但是,它会导致堆污染( heap pollution ),详见第 32 条 : 数组的运行时类型与它 的编译时类型不匹配(除非 E 正好是 Object ) 。 这使得有些程序员会觉得很不舒服,因而选择第二种方案,虽然堆污染在这种情况下并没有什么危害 。

        下面的程序示范了泛型 Stack 类的使用方法 。 程序以倒序的方式打印出它的命令行参数,并转换成大写字母 。 如果要在从堆战中弹出的元素上调用 String 的 toUpperCase方法,并不需要显式的转换,并且确保自动生成的转换会成功:

public static void main(String[] args) {
    Stack<String> stack = new Stack<>();
    for (String arg : args)
        stack.push(arg);
    while (!stack.isEmpty())
        System.out.println(stack.pop().toUpperCase();
}

        看来上述的示例与第 28 条相矛盾了,第 28 条鼓励优先使用列表而非数组 。 实际上不可能总是或者总想在泛型中使用列表 。Java 并不是生来就支持列表,因此有些泛型如 ArrayList,必须在数组上实现 。 为了提升性能,其他泛型如 HashMap 也在数组上实现 。

        总而言之,使用泛型比使用需要在客户端代码中进行转换的类型来得更加安全,也更加容易。 在设计新类型的时候,要确保它们不需要这种转换就可以使用 。 这通常意味着要把类做成是泛型的。 只要时间允许,就把现有的类型都泛型化 。 这对于这些类型的新用户来说会变得更加轻松,又不会破坏现有的客户端 。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

创新引领城市进化:人工智能和大数据塑造智慧城市新面貌

人工智能和大数据等前沿技术正以惊人的速度融入智慧城市的方方面面&#xff0c;为城市的发展注入了强大的智慧和活力。这些技术的应用不仅令城市管理更高效、居民生活更便捷&#xff0c;还为可持续发展和创新奠定了坚实的基础。 在智慧城市中&#xff0c;人工智能技术正成为城市…

分享一组天气组件

先看效果&#xff1a; CSS部分代码&#xff08;查看更多&#xff09;&#xff1a; <style>:root {--bg-color: #E9F5FA;--day-text-color: #4DB0D3;/* 多云 */--cloudy-background: #4DB0D3;--cloudy-temperature: #E6DF95;--cloudy-content: #D3EBF4;/* 晴 */--sunny-b…

TypeScript 中【class类】与 【 接口 Interfaces】的联合搭配使用解读

导读&#xff1a; 前面章节&#xff0c;我们讲到过 接口&#xff08;Interface&#xff09;可以用于对「对象的形状&#xff08;Shape&#xff09;」进行描述。 本章节主要介绍接口的另一个用途&#xff0c;对类的一部分行为进行抽象。 类配合实现接口 实现&#xff08;impleme…

中科亿海微RAM使用

引言 FPGA&#xff08;Field Programmable Gate Array&#xff0c;现场可编程门阵列&#xff09;是一种可编程逻辑设备&#xff0c;能够根据特定应用的需求进行配置和重新编程。在FPGA中&#xff0c;RAM&#xff08;Random Access Memory&#xff0c;随机存取存储器&#xff09…

Maven在IDEA2021版本中全局配置(一次配置处处生效)

前言 我们在开发中&#xff0c;Maven是必不可少的&#xff0c;但是每次都需要设置一遍Maven的仓库和settings.xml。真的是心累&#xff0c;今天教大家全局配置一下。再也不要每次项目都配了&#xff0c;Maven还经常出问题。 解决方案 友情提示&#xff1a;小编的IDEA版本为2…

【Fegin技术专题】「原生态」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(中)

你可以使用 Jersey 和 CXF 这些来写一个 Rest 或 SOAP 服务的java客服端。 你也可以直接使用 Apache HttpClient 来实现。但是 Feign 的目的是尽量的减少资源和代码来实现和 HTTP API 的连接。 *通过自定义的编码解码器以及错误处理&#xff0c;你可以编写任何基于文本的 HTT…

改进DevSecOps框架的 5 大关键技术

Markets and Markets的一项研究显示&#xff0c;全球DevOps的市场规模从2017年的29亿美元增加到2023年的103.1亿美元&#xff0c;预测期的年复合增长率(CAGR)为24.7%。人们对DevOps越来越感兴趣&#xff0c;因为DevOps不仅能够压缩软件的交付周期&#xff0c;还能提高交付的速度…

c++QT文件操作

1 介绍 QT的文件操作来源于其抽象基类QIODevice&#xff0c;中用于处理输入输出设备。提供了统一的接口来处理不同类型的数据源&#xff0c;如文件、套接字、缓冲区等。QIODevice 主要用于读取和写入数据&#xff0c;无论数据来自何种源头&#xff0c;都可以通过 QIODevice 统一…

HDFS中snapshot快照机制

HDFS中snapshot快照机制 介绍作用功能实现相关命令和操作相关命令 介绍 snapshot是数据存储的某一时刻的状态记录&#xff0c;备份&#xff08;backup&#xff09;则是数据存储的某一个时刻的副本HDFS snapshot快照是整个文件系统或某个目录在某个时刻的镜像&#xff0c;该镜像…

安路FPGA的赋值报错——移位处理,加括号

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 在使用移位符号用来当作除以号使用时&#xff0c;发现如下问题 其中 cnt_8K 为偶数和奇数时输出的数据不一样 reg [10:0] cnt_8K; reg [10:0] ram1_addra; always(posedge clk_16M) begin if(ram_out_flag )begin if(…

20230811导出Redmi Note12Pro 5G手机的录音机APP的录音

20230811导出Redmi Note12Pro 5G手机的录音机APP的录音 2023/8/11 10:54 redmi note12 pro 录音文件 位置 貌似必须导出录音&#xff0c;录音的源文件不知道存储到哪里了&#xff01; 参考资料&#xff1a; https://jingyan.baidu.com/article/b87fe19e9aa79b1319356842.html 红…

【MySQL】InnoDB存储引擎详解

InnoDB引擎是MySQL5.5版本之后默认的存储引擎 逻辑存储结构 首先是表空间Tablespace&#xff08;ibd文件&#xff09;&#xff1a;一个mysql实力可以对应多个表空间&#xff0c;用于存储及记录&#xff0c;索引等数据 这些存储记录&#xff0c;索引等数据中是用段(Segment)来…

宋浩概率论笔记(四)数字特征

本帖更新数字特征&#xff0c;包含期望、方差、相关系数等&#xff0c;要点在于记忆性质中的各种公式&#xff0c;遇到题目时能迅速利用已知条件计算答案。

PXE-kickstart无人值守安装操作系统

PXE的概念&#xff1a; PXE&#xff08;Pre-boot Execution Environment&#xff0c;预启动执行环境&#xff09;是由Intel公司开发的最新技术&#xff0c;工作于C/S的网络模式&#xff0c;支持工作站通过网络从远端服务器下载映像&#xff0c;并由此支持通过网络启动操作系统…

QT网络编程之TCP

QT网络编程之TCP TCP 编程需要用到俩个类: QTcpServer 和 QTcpSocket。 #------------------------------------------------- # # Project created by QtCreator 2023-08-

生产执行MES系统:提升企业灵活性和响应速度的关键利器

在竞争激烈的市场环境下&#xff0c;企业需要不断提高其灵活性和响应速度&#xff0c;以适应快速变化的需求和市场动态。生产执行MES&#xff08;Manufacturing Execution System&#xff09;系统作为信息技术的重要应用&#xff0c;为企业提供了强大的工具和平台&#xff0c;能…

【JavaScript】new 的原理以及实现

网道 - new 命令的原理 使用new命令时&#xff0c;它后面的函数依次执行下面的步骤。 创建一个空对象&#xff0c;作为将要返回的对象实例。将这个空对象的原型&#xff0c;指向构造函数的prototype属性。将这个空对象赋值给函数内部的this关键字。如果构造函数返回了一个对象…

开封Geotrust单域名https证书推荐

Geotrust作为全球领先的数字证书颁发机构之一&#xff0c;拥有多年的数字证书颁发经验&#xff0c;其数字证书被广泛应用于电子商务、在线支付、企业通讯、云计算等领域&#xff0c;为用户提供了安全可靠的保障。而Geotrust旗下的单域名https证书是大多数客户创建网站时的选择之…

java spring cloud 企业电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展 tbms

​ 项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以…

vscode关闭绑定元素“xxx”隐式具有“any”类型这类错误

在ts的项目里面&#xff0c;真的经常看到any类型的报错&#xff0c;真的很烦的 所以为了眼不见心不乱&#xff0c;我决定消除这个错误提示 在tsconfig.json里面配置 "noImplicitAny": false 就可以了 {"compilerOptions": {"target": "E…