Effective Java笔记(31)利用有限制通配符来提升 API 的灵活性

news2024/11/29 12:46:46

        参数化类型是不变的( invariant ) 。 换句话说,对于任何两个截然不同的类型 Typel 和 Type2 而言, List<Type1 >既不是 List<Type 2 > 的子类型,也不是它的超类型 。虽然 L ist<String>不是 List<Object>的子类型,这与直觉相悖,但是实际上很有意义 。你可以将任何对象放进一个List<Object>中,却只能将字符串放进 List<String>中 。由于 List<String>不能像 List<O句ect> 能做任何事情,它不是一个子类型 。

        有时候,我们需要的灵活性要比不变类型所能提供的更多 。比如第 29 条中的堆楼 。 提醒一下,下面就是它的公共 API:

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

        假设我们想要增加一个方法,让它按顺序将一系列的元素全部放到堆枝中 。 第一次尝试如下:

public void pushAll(Iterable<E> src) {
    for(E e : src)
        push(e);
}

        这个方法编译时正确无误,但是并非尽如人意 。 如果 Iterable 的 src 元素类型与堆栈的完全匹配,就没有问题 。 但是假如有一个 Stack<Number>,并且调用了 push (intVal),这里的工ntVal 就是 Integer 类型 。 这是可以的,因为 Integer 是 Number 的一个子类型 。 因此从逻辑上来说,下面这个方法应该可行 :

Stack <Number> numberStack = new Stack<>() ;
Iterable<Integer> integers = ...;
numberStack. pushAll(integers);

但是,如果尝试这么做,就会得到下面的错误消息,因为参数化类型是不可变的:

        幸运的是,有一种解决办法 。Java 提供了一种特殊的参数化类型,称作有限制的通配符类型(bounded wildcard type ),它可以处理类似的情况 。pushAll 的输入参数类型不应该为“ E 的 Iterable 接口”,而应该为“ E 的某个子类型的 Iterable 接口”通配符类型Iterable<?extends E >正是这个意思 。 (使用关键字 ex ten也有些误导 :回忆一下第29 条中的说法,确定了子类型( subtype )后,每个类型便都是自身的子类型,即使它没有将自身扩展 。)我们修改一下 pushAll 来使用这个类型:

public void pushAll(Iterable<? extends E> src) {
    for(E e : src)
        push(e);
}

        修改之后,不仅 Stack 可以正确无误地编译,没有通过初始的 pushAll 声明进行编译的客户端代码也一样可以 。 因为 Stack 及其客户端正确无误地进行了编译,你就知道一切都是类型安全的了 。

        现在假设想要编写一个 pushAll 方法,使之与 popAll 方法相呼应 。popAll 方法从堆校中弹出每个元素,并将这些元素添加到指定的集合中 。 初次尝试编写的 popAll 方法可能像下面这样 :

public void popAll(Col1ection<E> dst) {
    while (!isEmpty())
        dst.add(pop());
}

        此外,如果目标集合的元素类型与堆栈的完全匹配,这段代码编译时还是会正确无误,并且运行良好 。 但是,也并不意味着尽如人意 。 假设你有一个 Stack<Number >和 Object 类型的变量 。 如果从堆校中弹出 一个元素,并将它保存在该变量中,它的编译和运行都不会出错,那你为何不能也这么做呢?

Stack<Number> numberStack = new Stack<Number>() ;
Collection<Object> objects = ...;
numberStack.popAll(objects) ;

        如果试着用上述 的 popAll 版本编译这段客户端代码,就会得到一个非常类似于第一次用 pushAll 时所得到的错误:Collection<Object >不是 Collection<Number>的子类型 。 这一次通配符类型同样提供了一种解决办法 。popAll 的输入参数类型不应该为“ E 的集合”,而应该为“ E 的某种超类的集合”(这里的超类是确定的,因此 E 是它自身的一个超类型)。 仍有一个通配符类型正符合此意:Collection<? super E > 。 让我们修改 popAll 来使用它:

public void popAll(Collection<? super E> dst) {
    while (!isEmpty())
        dst.add(pop();
}

        做了 这个变动之后,Stack 和客户端代码就都可以正确无误地编译了 。

        结论很明显:为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型 。 如果某个输入参数既是生产者,又是消费者,那么通配符类型对你就没有什么好处了:因为你需要的是严格的类型匹配,这是不用任何通配符而得到的 。

        下面的助记符便于让你记住要使用哪种通配符类型 :

        PECS 表示 producer-extends,consumer-super

        换句话说,如果参数化类型表示一个生产者 T ,就使用<? extends T >;如果它表示一个消 费者 T ,就使用 <? super T > 。 在我们的 Stack 示例中,pushAll 的 src 参数产生 E 实 例供 Stack 使用 ,因 此 src 相 应的类型为 Iterable<? extends E> ; popAll的 dst 参数通过 Stack 消费 E 实例,因此 dst 相应的类型为 Collection<? s uper E > 。PECS 这个助记符突 出了使用通配符类型的基本原则 。Naftalin 和 Wadler 称之为 Get αnd Put Principle。

        如果使用得当,通配符类型对于类的用户来说几乎是无形的 。 它们使方法能够接受它们应该接受的参数,并拒绝那些应该拒绝的参数 。 如果类的 用 户必须考虑通配符类型,类的API 或许就会出错

        一般来说, 如果类型参数只在方法声明中出现一次,就可以用通配符取代它 。 如果是无限制的类型参数,就用无限制的通配符取代它;如果是有限制的类型参数,就用有限制的通配符取代它。

        总而言之,在 API 中使用通配符类型虽然比较需要技巧,但是会使 API 变得灵活得多 。 如果编写 的是将被广泛使用的类库, 则一定要适当地利用通配符类型 。 记住基本的原则:producer-extends,consumer-super(PECS ) 。 还要记住所有的 comparable 和comparator 都是消费者 。

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

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

相关文章

问道管理:零基础学炒股?

跟着股市的不断升温&#xff0c;越来越多的人参加到了炒股大军中&#xff0c;希望经过股市赚到更多的金钱。但是关于零根底的新手来说&#xff0c;怎么开始学习炒股成为了一个难题。那么&#xff0c;零根底学炒股真的难吗&#xff1f;怎么更好的入门和学习&#xff1f; 首要&am…

线性代数(三) 线性方程组

前言 如何利用行列式&#xff0c;矩阵求解线性方程组。 线性方程组的相关概念 用矩阵方程表示 齐次线性方程组&#xff1a;Ax0&#xff1b;非齐次线性方程组&#xff1a;Axb. 可以理解 齐次线性方程组 是特殊的 非齐次线性方程组 如何判断线性方程组的解 其中R(A)表示矩阵A的…

模型、策略和算法

模型(model)、策略(strategy)和算法(algorithm)是统计学习的所有内容. 模型是统计学习的最终结果&#xff0c;即决策函数(decision function) 或条件概率函数 &#xff0c;它被⽤来预测特定问题下&#xff0c;将来未知输⼊的输出结果. 策略是统计学习过程中的产⽣最优模型的评…

如何使用vue创建一个项目

1、安装node.js 打开cmd 输入node -v 和npm -v查看电脑是否已经安装了node,js 如果出现以下截图&#xff0c;则说明已经有node.js&#xff0c;可忽略此步 如果没有&#xff0c;则打开官网&#xff0c;下载即可 下载地址&#xff1a;Node.js 下载完成后&#xff0c;打开cmd输…

漏洞挖掘日记1:企业src某系统存在登录绕过漏洞

&#xff08;一&#xff09;漏洞描述 这个漏洞属于逻辑漏洞&#xff0c;逻辑漏洞是指开发者在开发过程中&#xff0c;实现业务上出现了逻辑上的漏洞。之所以出现逻辑漏洞&#xff0c;是因为一些开发者&#xff0c;在开发过程中&#xff0c;第一考虑是怎么实现功能&#xff0c;…

OPENCV C++(十)gramm矫正+直方图均衡化

两者都是只对单通道使用&#xff0c;对多通道的话 就需要分离通道处理再合并通道 两种方法&#xff0c;第一个要运算次数太多了&#xff0c;第二个只需要查表 伽马矫正函数&#xff0c;这里用第二种方法&#xff0c;且写法有点高级 int gammaCorrection(cv::Mat srcMat, cv::…

路由器和交换机的区别

交换机和路由器的区别 交换机实现局域网内点对点通信&#xff0c;路由器实现收集发散&#xff0c;相当于一个猎头实现的中介的功能 路由器属于网络层&#xff0c;可以处理TCP/IP协议&#xff0c;通过IP地址寻址&#xff1b;交换机属于中继层&#xff0c;通过MAC地址寻址(列表)…

聚观早报 | 真我GT5系列工艺细节曝光;小米MIXFold3定妆照抢先看

【聚观365】8月10日消息 真我GT5系列工艺细节曝光小米MIX Fold 3定妆照抢先看360智脑整体能力提升15%科大讯飞智能办公本X3正式发布索尼第二季度营收增长33% 真我GT5系列工艺细节曝光 在此前举办的ChinaJoy2023上&#xff0c;作为手机行业新势力的真我realme携手旗下多款爆款…

JavaWeb 速通Session

目录 一、Session的引入 1.什么是Session&#xff1f; 2.Session的基本原理 : 3.Session长什么样子&#xff1f; 二、Session的底层机制 1. Session接口相关的方法 : 1 req.getSession() : 2 httpSession.setAttribute(String name, Object val); 3 Object obj httpSessi…

Python(七十五--总结)列表、字典、元组、集合总结

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

【Python篇】Python基础语法

【Python篇】Python基础语法 拖拖拖&#xff0c;能使工作便捷高效的为何要拒绝&#xff0c;作个记录—【蘇小沐】 文章目录 【Python篇】Python基础语法1.实验环境 1、标识符2、Python保留字&#xff08;关键字&#xff1a;不能用作任何标识符名称&#xff09;3、注释1&#x…

定制全彩LED电子屏的模组类型

定制全彩LED电子屏的成功与否很大程度上取决于选择合适的LED模组。LED模组是LED电子屏的基本组成部分&#xff0c;直接影响显示效果、可靠性和性能。什么是LED模组&#xff1f;根据LED屏表面处理的工艺不同&#xff0c;我们可以把其分为插件模组、表贴模组、亚表贴模组、三合一…

Redis 搭建哨兵集群

文章目录 0. 哨兵原理1. 哨兵集群架构2. 准备实例和配置3. 启动4. 测试5. RedisTemplate 的哨兵模式配置地址配置读写分离 在 主从架构 Redis 搭建主从集群 中&#xff0c;一个 slave 节点挂了无影响&#xff0c;但是 master 节点挂了&#xff0c;就无法进行写操作了&#xff0…

代码随想录算法训练营之JAVA|第二十五天| 491. 递增子序列

今天是第25天刷leetcode&#xff0c;立个flag&#xff0c;打卡60天。 算法挑战链接 491. 递增子序列https://leetcode.cn/problems/non-decreasing-subsequences/ 第一想法 题目理解&#xff1a;在给定的一个数组中&#xff0c;找出全部的递增列表。要求不能有重复。 这是一…

Talk | 清华大学交叉信息研究院助理教授许华哲:具身控制中的泛化能力

本期为TechBeat人工智能社区第520期线上Talk&#xff01; 北京时间8月9日(周三)20:00&#xff0c;清华大学交叉信息研究院助理教授—许华哲的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “具身控制中的泛化能力”&#xff0c;从具身控制中视觉外…

python采集淘宝整店商品 json格式

竞争优势&#xff1a;通过采集淘宝整店商品&#xff0c;可以获取到同一行业或同一类别的竞争对手的商品信息。这使得你可以更好地了解市场上的产品&#xff0c;了解竞争对手的定价、销售策略和产品特点&#xff0c;从而更好地制定自己的营销策略和定价策略。在竞争激烈的市场中…

支持对接鸿蒙系统的无线模块及其常见应用介绍

近距离的无线通信得益于万物互联网的快速发展&#xff0c;基于集成部近距离无线连接&#xff0c;为固定和移动设备建立通信的蓝牙技术也已经广泛应用于汽车领域、工业生产及医疗领域。为协助物联网企业终端产品能快速接入鸿蒙生态系统&#xff0c;SKYLAB联手国产芯片厂家研发推…

爬虫与搜索引擎优化:通过Python爬虫提升网站搜索排名

作为一名专业的爬虫程序员&#xff0c;我深知网站的搜索排名对于业务的重要性。在如今竞争激烈的网络世界中&#xff0c;如何让自己的网站在搜索引擎结果中脱颖而出&#xff0c;成为关键。今天&#xff0c;和大家分享一些关于如何通过Python爬虫来提升网站的搜索排名的技巧和实…

android APP内存优化

Android为每个应用分配多少内存 Android出厂后&#xff0c;java虚拟机对单个应用的最大内存分配就确定下来了&#xff0c;超出这个值就会OOM。这个属性值是定义在/system/build.prop文件中. 例如&#xff0c;如下参数 dalvik.vm.heapstartsize8m #起始分配内存 dalvik.vm.…

2023企业数智化转型的正确打开方式是什么?他这样说(二)

哈喽~又见面了大家&#xff01;上期我们说到了数据在数智化转型中的重要性&#xff0c;戳这里↓↓↓&#xff0c;一键直达 2023企业数智化转型的正确打开方式是什么&#xff1f;他这样说&#xff08;一&#xff09; 这期我们将从监控的角度进一步跟大家分享数智化转型&#x…