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

        参数化类型是不变的( 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.mzph.cn/news/34896.shtml

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

相关文章

Linux 文件查看命令

一、cat命令 1.cat文件名&#xff0c;查看文件内容&#xff1a; 例如&#xff0c;查看main.c文件的内容&#xff1a; 2.cat < 文件名&#xff0c;往文件中写入数据&#xff0c; Ctrld是结束输入 例如&#xff0c;向文件a.txt中写入数据&#xff1a; 查看刚刚写入a.txt的…

Yolov5(一)VOC划分数据集、VOC转YOLO数据集

代码使用方法注意修改一下路径、验证集比例、类别名称&#xff0c;其他均不需要改动&#xff0c;自动划分训练集、验证集、建好全部文件夹、一键自动生成Yolo格式数据集在当前目录下&#xff0c;大家可以直接修改相应的配置文件进行训练。 目录 使用方法&#xff1a; 全部代码…

解决监督学习,深度学习报错:AttributeError: ‘xxx‘ object has no attribute ‘module‘!!!!

哈喽小伙伴们大家好呀&#xff0c;很长时间没有更新啦&#xff0c;最近在研究一个问题&#xff0c;就是AttributeError: xxx object has no attribute module 今天终于是解决了&#xff0c;所以来记录分享一下&#xff1a; 我这里出现的问题是&#xff1a; 因为我的数据比较大…

SQL优化

一、插入数据 优化 1.1 普通插入&#xff08;小数据量&#xff09; 普通插入&#xff08;小数据量&#xff09;&#xff1a; 采用批量插入&#xff08;一次插入的数据不建议超过1000条&#xff09;手动提交事务主键顺序插入 1.2 大批量数据插入 大批量插入&#xff1a;&…

数据结构:力扣OJ题

目录 ​编辑题一&#xff1a;链表分割 思路一&#xff1a; 题二&#xff1a;相交链表 思路一&#xff1a; 题三&#xff1a;环形链表 思路一&#xff1a; 题四&#xff1a;链表的回文结构 思路一&#xff1a; 链表反转&#xff1a; 查找中间节点&#xff1a; 本人实力…

YOLOv8+ByteTrack多目标跟踪(行人车辆计数与越界识别)

课程链接&#xff1a;https://edu.csdn.net/course/detail/38901 ByteTrack是发表于2022年的ECCV国际会议的先进的多目标跟踪算法。YOLOv8代码中已集成了ByteTrack。本课程使用YOLOv8和ByteTrack对视频中的行人、车辆做多目标跟踪计数与越界识别&#xff0c;开展YOLOv8目标检测…

第一百二十七天学习记录:我的创作纪念日

机缘 今天收到CSDN官方的来信&#xff0c;想想也可以对我前面的学习记录进行一个总结。 关于来到CSDN的初心&#xff0c;也就是为了让自己养成一个良好的学习总结的习惯。这里要感谢我C语言视频教程的老师&#xff0c;是他建议学生们在技术博客中进行记录。对于技术博客&…

web-Element

在vueapp里<div><!-- <h1>{{message}}</h1> --><element-view></element-view></div> <div><!-- <h1>{{message}}</h1> --><element-view></element-view></div>在view新建个文件 <t…

vue或uniapp使用pdf.js预览

一、先下载稳定版的pdf.js&#xff0c;可以去官网下载 官网下载地址 或 pdf.js包下载(已配置好&#xff0c;无需修改) 二、下载好的pdf.js文件放在public下静态文件里&#xff0c; uniapp是放在 static下静态文件里 三、使用方式 1. vue项目 注意路径 :src"static/pd…

每日一题 206反转链表

题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a; …

块、行内块水平垂直居中

1.定位实现水平垂直居中 <div class"outer"><div class"test inner1">定位实现水平垂直居中</div></div><style>.outer {width: 300px;height: 300px;border: 1px solid gray;margin: 100px auto 0;position: relative;}.te…

途乐证券-新股行情持续火爆,哪些因素影响首日表现?

全面注册制以来&#xff0c;参加打新的投资者数量全体呈现下降。打新收益下降&#xff0c;破发频出的布景下&#xff0c;投资者打新策略从逢新必打逐步向优选个股改变。 经过很多历史数据&#xff0c;从商场定价、参加者热度以及机构重视度维度揭秘了上市后股价体现优秀的个股具…

Redis 之 缓存预热 缓存雪崩 缓存击穿 缓存穿透

目录 一、缓存预热 1.1 缓存预热是什么&#xff1f; 1.2 解决方案&#xff1a; 二、缓存雪崩 2.1 缓存雪崩是什么&#xff1f;怎么发生的&#xff1f; 2.2 怎么解决 三、缓存穿透 3.1 是什么&#xff1f;怎么产生的呢&#xff1f; 3.2 解决方案 3.2.1、采用回写增强&a…

SpringBoot基础之注册Servlet三大组件

文章目录 前言一、介绍二、注入Bean2.1.ServletRegistrationBean2.2.FilterRegistrationBean2.3.ServletListenerRegistrationBean 三.演示结果总结 前言 本文章将介绍SpringBoot注册Servlet的三大组件 一、介绍 由于SpringBoot默认是以jar包的方式运行嵌入式Servlet容器来启…

Protues如何安装下载使用:STM32利用Protues进行仿真

文章目录&#xff1a; 一&#xff1a;Proteus仿真的使用步骤 第一步&#xff1a;Proteus新建项目 第二步&#xff1a;Proteus设计电路图&#xff08;选取元器件、摆放元器件、编辑元器件属性、原理图布线&#xff09; 第三步&#xff1a;程序代码编写 第四步&#xff1a;…

如何在CSS中水平居中一个元素?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用 margin: 0 auto⭐ 使用 Flexbox 布局⭐ 使用绝对定位和负边距⭐ 使用表格布局⭐ 使用网格布局⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅…

VUE3组件

组件基础 {#components-basics} 组件允许我们将 UI 划分为独立的、可重用的部分&#xff0c;并且可以对每个部分进行单独的思考。在实际应用中&#xff0c;组件常常被组织成层层嵌套的树状结构&#xff1a; 这和我们嵌套 HTML 元素的方式类似&#xff0c;Vue 实现了自己的组件…

《使用 VMware 在 Windows 上搭建 Linux 系统的完整指南》

《使用 VMware 在 Windows 上搭建 Linux 系统的完整指南》 1、准备工作1.1 安装 VMware 软件1.2 下载 Linux 发行版镜像文件1.3 安装SSH工具 2、创建新的虚拟机2.1 VMware页面2.2 打开VMware页面并点击创建新的虚拟机&#xff0c;选择自定义2.3 选择系统兼容性&#xff0c;默认…

微信小程序读取本地json

首先在项目录下新建【server】文件夹&#xff0c;新建data.js文件&#xff0c;并定义好json数据格式。如下&#xff1a; pages/index/index.ts导入data.js并请求json pages/index/index.wxml页面展示数据

PHP实践:分布式场景下的Session共享解决方案实现

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责…