【Java】泛型 之 extends通配符
我们前面已经讲到了泛型的继承关系:Pair<Integer>不是Pair<Number>的子类。
假设我们定义了Pair<T>:
public class Pair<T> { ... }
然后,我们又针对Pair<Number>类型写了一个静态方法,它接收的参数类型是Pair<Number>:
public class PairHelper {static int add(Pair<Number> p) {Number first = p.getFirst();Number last = p.getLast();return first.intValue() + last.intValue();}
}
上述代码是可以正常编译的。使用的时候,我们传入:
int sum = PairHelper.add(new Pair<Number>(1, 2));
注意:传入的类型是Pair<Number>,实际参数类型是(Integer, Integer)。
既然实际参数是Integer类型,试试传入Pair<Integer>:
public class Main {public static void main(String[] args) {Pair<Integer> p = new Pair<>(123, 456);int n = add(p);System.out.println(n);}static int add(Pair<Number> p) {Number first = p.getFirst();Number last = p.getLast();return first.intValue() + last.intValue();}}class Pair<T> {private T first;private T last;public Pair(T first, T last) {this.first = first;this.last = last;}public T getFirst() {return first;}public T getLast() {return last;}
}
直接运行,会得到一个编译错误:
incompatible types: Pair<Integer> cannot be converted to Pair<Number>
原因很明显,因为Pair<Integer>不是Pair<Number>的子类,因此,add(Pair<Number>)不接受参数类型Pair<Integer>。
但是从add()方法的代码可知,传入Pair<Integer>是完全符合内部代码的类型规范,因为语句:
Number first = p.getFirst();
Number last = p.getLast();
实际类型是Integer,引用类型是Number,没有问题。问题在于方法参数类型定死了只能传入Pair<Number>。
有没有办法使得方法参数接受Pair<Integer>?办法是有的,这就是使用Pair<? extends Number>使得方法接收所有泛型类型为Number或Number子类的Pair类型。我们把代码改写如下:
public class Main {public static void main(String[] args) {Pair<Integer> p = new Pair<>(123, 456);int n = add(p);System.out.println(n);}static int add(Pair<? extends Number> p) {Number first = p.getFirst();Number last = p.getLast();return first.intValue() + last.intValue();}}class Pair<T> {private T first;private T last;public Pair(T first, T last) {this.first = first;this.last = last;}public T getFirst() {return first;}public T getLast() {return last;}
}
这样一来,给方法传入Pair<Integer>类型时,它符合参数Pair<? extends Number>类型。这种使用<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了。
除了可以传入Pair<Integer>类型,我们还可以传入Pair<Double>类型,Pair<BigDecimal>类型等等,因为Double和BigDecimal都是Number的子类。
如果我们考察对Pair<? extends Number>类型调用getFirst()方法,实际的方法签名变成了:
<? extends Number> getFirst();
即返回值是Number或Number的子类,因此,可以安全赋值给Number类型的变量:
Number x = p.getFirst();
然后,我们不可预测实际类型就是Integer,例如,下面的代码是无法通过编译的:
Integer x = p.getFirst();
这是因为实际的返回类型可能是Integer,也可能是Double或者其他类型,编译器只能确定类型一定是Number的子类(包括Number类型本身),但具体类型无法确定。
我们再来考察一下Pair<T>的set方法:
public class Main {public static void main(String[] args) {Pair<Integer> p = new Pair<>(123, 456);int n = add(p);System.out.println(n);}static int add(Pair<? extends Number> p) {Number first = p.getFirst();Number last = p.getLast();p.setFirst(new Integer(first.intValue() + 100));p.setLast(new Integer(last.intValue() + 100));return p.getFirst().intValue() + p.getFirst().intValue();}}class Pair<T> {private T first;private T last;public Pair(T first, T last) {this.first = first;this.last = last;}public T getFirst() {return first;}public T getLast() {return last;}public void setFirst(T first) {this.first = first;}public void setLast(T last) {this.last = last;}
}
不出意外,我们会得到一个编译错误:
incompatible types: Integer cannot be converted to CAP#1
where CAP#1 is a fresh type-variable:CAP#1 extends Number from capture of ? extends Number```
编译错误发生在`p.setFirst()`传入的参数是`Integer`类型。有些童鞋会问了,既然p的定义是`Pair<? extends Number>`,那么`setFirst(? extends Number)`为什么不能传入`Integer`?原因还在于擦拭法。如果我们传入的p是`Pair<Double>`,显然它满足参数定义`Pair<? extends Number`>,然而,`Pair<Double>`的`setFirst()`显然无法接受`Integer`类型。这就是`<? extends Number>`通配符的一个重要限制:方法参数签名`setFirst(? extends Number)`无法传递任何`Number`的子类型给`setFirst(? extends Number)`。这里唯一的例外是可以给方法参数传入null:
```java
p.setFirst(null); // ok, 但是后面会抛出NullPointerException
p.getFirst().intValue(); // NullPointerException
extends通配符的作用
如果我们考察Java标准库的java.util.List<T>接口,它实现的是一个类似“可变数组”的列表,主要功能包括:
public interface List<T> {int size(); // 获取个数T get(int index); // 根据索引获取指定元素void add(T t); // 添加一个新元素void remove(T t); // 删除一个已有元素
}
现在,让我们定义一个方法来处理列表的每个元素:
int sumOfList(List<? extends Integer> list) {int sum = 0;for (int i=0; i<list.size(); i++) {Integer n = list.get(i);sum = sum + n;}return sum;
}
为什么我们定义的方法参数类型是List<? extends Integer>而不是List<Integer>?从方法内部代码看,传入List<? extends Integer>或者List<Integer>是完全一样的,但是,注意到List<? extends Integer>的限制:
允许调用get()方法获取Integer的引用;
不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)。
因此,方法参数类型List<? extends Integer>表明了该方法内部只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)、remove(? extends Integer)这些方法。换句话说,这是一个对参数List<? extends Integer>进行只读的方法(恶意调用set(null)除外)。
使用extends限定T类型
在定义泛型类型Pair<T>的时候,也可以使用extends通配符来限定T的类型:
public class Pair<T extends Number> { ... }```
现在,我们只能定义:
```java
Pair<Number> p1 = null;
Pair<Integer> p2 = new Pair<>(1, 2);
Pair<Double> p3 = null;
因为Number、Integer和Double都符合<T extends Number>。
非Number类型将无法通过编译:
Pair<String> p1 = null; // compile error!
Pair<Object> p2 = null; // compile error!
因为String、Object都不符合<T extends Number>,因为它们不是Number类型或Number的子类。
小结
使用类似<? extends Number>通配符作为方法参数时表示:
方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();;
方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);。
即一句话总结:使用extends通配符表示可以读,不能写。
使用类似<T extends Number>定义泛型类时表示:
泛型类型限定为Number以及Number的子类。
相关文章:
【Java】泛型 之 extends通配符
我们前面已经讲到了泛型的继承关系:Pair<Integer>不是Pair<Number>的子类。 假设我们定义了Pair<T>: public class Pair<T> { ... }然后,我们又针对Pair<Number>类型写了一个静态方法,它接收的参…...
光谱-空间特征分割提取:多光谱图像压缩
Spectral–Spatial Feature Partitioned Extraction Based on CNN for Multispectral Image Compression (基于CNN的光谱-空间特征分割提取多光谱图像压缩) 近年来,多光谱成像技术的迅速发展引起了各领域的高度重视,这就不可避免…...
绝缘子主要尺寸
声明 本文是学习GB-T 1000-2016 高压线路针式瓷绝缘子尺寸与特性. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了高压线路针式瓷绝缘子的结构型式、尺寸、机械特性和电气特性。 本标准适用于标称电压为10 kV 及以下、频率不高于…...
什么是哈希表?如何使用哈希表进行数据存储和查找?
什么是哈希表? 哈希表(Hash Table),也被称为散列表,是一种用于存储键值对数据的数据结构。它是一种非常高效的数据结构,可以实现快速的数据插入、查找和删除操作。哈希表的核心思想是通过将键(…...
脑机接口的发展研究
1.调研对象:智能制造人机交互; 2.作业内容,关于个人所选技术的近期发展(2020-2023年)期间的相关技术问题和研究进展; 3.内容结构,分为摘要介绍(100字以内),近…...
短期光伏发电量短期预测(Python代码,先对异常值处理,再基于XGBoost模型预测)
一.代码流程(运行效果:短期光伏发电量短期预测(Python代码,先对异常值处理,再基于XGBoost模型预测)_哔哩哔哩_bilibili 模型流程: 导入所需的库,包括NumPy、Pandas、Matplotlib、Sea…...
SpringCloud Gateway--Predicate/断言(详细介绍)中
😀前言 本篇博文是关于SpringCloud Gateway–Predicate/断言(详细介绍)中,希望你能够喜欢 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以…...
Linux内核启动流程-第一阶段汇编流程简介
一. Linux启动流程 看完 Linux 内核的顶层 Makefile 以后再来看 Linux 内核的大致启动流程, Linux 内核的启 动流程要比 uboot 复杂的多,涉及到的内容也更多。 本文中,我们就大致的了解一下 Linux 内 核的启动流程。 要分析 Li…...
SpringBoot-Druid
目录 1.什么是Druid 2.主要优点和原因 3.误区 4.Part代码 0.pom 1.Spring.datasource.type: com.alibaba.druid.pool.DruidDataSource 2.Druid用Jasypt加密任意内容 EnableEncryptableProperties开启加密注解 3.Druid监控平台 1.什么是Druid Druid 是一个开源的数据库…...
PAT甲级真题1006:签到与签出
🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…...
【架构篇】Supabase架构和功能介绍
Supabase是什么 Supabase将自己定位为Firebase的开源替代品,提供了一套工具来帮助开发者构建web或移动应用程序。Supabase是建立在Postgres之上的,Postgres是一个免费的开源数据库,被认为是世界上最稳定、最先进的数据库之一。Supabase对标F…...
Github主页无法打开和Assets转圈
1、cmd启动命令行 2、github.com打不开,多刷新几遍。等成功打开时,命令行输入nslookup github.com,把非权威应答下的IP地址复制到C:\Windows\System32\drivers\etc\hosts里,如查到的IP是192.30.255.112,则填写 192.30.255.112 gi…...
rm误删文件恢复
rm误删文件恢复 问题描述安装extundeleteyum安装extundelete编译安装extundelete 常用参数动作(action): 尝试数据恢复前置条件卸载磁盘分区查看被删除数据信息 恢复文件恢复指定inode号文件恢复指定文件名恢复指定目录恢复所有可恢复文件恢复指定时间的文件恢复指定…...
爬虫 — 多线程
目录 一、多任务概念二、实现多任务方式1、多进程 (Multiprocessing)2、多线程(Multithreading)3、协程(Coroutine) 三、多线程执行顺序四、多线程的方法1、join()2、setDaemon()3、threading.enumerate() …...
Cython 笔记 (Python/Jython)
目录 1. Cython 笔记 (Python)2. python 加速库 cython 简介2.1. Cython 是什么?2.2. 如何安装 Cython?2.3. 简单示例2.4. 性能比对2.5. 总结 3. PYTHON, CYTHON, JYTHON, IRONPYTHON 的区别 (注意: 此篇有误导,表述不一定正确,只提供一个方向)3.1. PY…...
[React] react-hooks如何使用
react-hooks思想和初衷,也是把组件,颗粒化,单元化,形成独立的渲染环境,减少渲染次数,优化性能。 文章目录 1.为什么要使用hooks2.如何使用hooks2.1 useState2.2 useEffect2.3 useLayoutEffect2.4 useRef2.5…...
多个pdf合并成一个文件,3个方法合并pdf
如何把多个pdf合并成一个文件?在我们日常的工作中,经常会遇到一些需要处理的文件,其中包括PDF文件。特别是当我们需要将多个PDF文件合并成一个PDF文件时,会面临一些困难。这样的情况下,我们的阅读能力会受到限制&#…...
代码随想录 动态规划Ⅸ
198. 打家劫舍 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个…...
【数据结构】散列表(哈希表)的学习知识总结
目录 1、散列表 2、散列函数 2.1 定义 2.2 散列函数的构造 2.2.1 除留余数法 2.2.2 直接定址法 2.2.3 数字分析法 2.2.4 平方取中法 3、冲突(碰撞) 4、处理冲突的方法 4.1 拉链法(链接法) 4.2 开放定址法 5、C语言…...
2023智慧云打印小程序源码多店铺开源版 +前端
智慧自助云打印系统/智慧云打印小程序源码 前端 这是一款全新的基于Thinkphp的最新自助打印系统,最新UI界面设计的云打印小程序源码...
ESP32智能硬件开发实战指南:从环境搭建到AI功能落地
ESP32智能硬件开发实战指南:从环境搭建到AI功能落地 【免费下载链接】xiaozhi-esp32 An MCP-based chatbot | 一个基于MCP的聊天机器人 项目地址: https://gitcode.com/GitHub_Trending/xia/xiaozhi-esp32 如何在复杂的硬件开发中快速实现AI功能集成…...
Hunyuan-MT-7B像素翻译终端效果实测:中英日韩四语同屏对照演示
Hunyuan-MT-7B像素翻译终端效果实测:中英日韩四语同屏对照演示 1. 产品概览与核心特色 1.1 像素化翻译新体验 Pixel Language Portal(像素语言跨维传送门)是一款基于Tencent Hunyuan-MT-7B大模型构建的创新翻译工具。与传统翻译软件不同&a…...
STM32CubeMX项目实战:从新建工程到驱动LED,一步步教你玩转HAL库(附代码解析)
STM32CubeMX实战指南:HAL库驱动LED的底层逻辑与工程化思维 第一次打开STM32CubeMX时,那种面对密密麻麻的配置选项却不知从何下手的焦虑感,相信每位嵌入式开发者都记忆犹新。不同于传统寄存器操作的直白,HAL库和图形化配置工具带来…...
幻兽帕鲁存档迁移完全手册:告别数据丢失的终极解决方案
幻兽帕鲁存档迁移完全手册:告别数据丢失的终极解决方案 【免费下载链接】palworld-host-save-fix 项目地址: https://gitcode.com/gh_mirrors/pa/palworld-host-save-fix 你是否曾在更换幻兽帕鲁服务器时,眼睁睁看着自己辛苦培养的角色数据消失无…...
MoveIt2新手必看:如何正确选择安装分支(main vs. tutorials)及使用vcs管理多仓库
MoveIt2分支选择与多仓库管理实战指南 当你在ROS2生态中开始使用MoveIt2时,第一个拦路虎往往不是算法理解或代码编写,而是如何正确搭建开发环境。MoveIt2作为由数十个独立Git仓库组成的复杂项目,其分支管理和版本协同问题困扰着许多中级开发者…...
企业信息化升级必备:OA系统的功能与优势
企业信息化升级,OA系统开启高效办公新时代在当今数字化飞速发展的时代,企业的信息化升级已成为提升竞争力的关键。而OA系统,作为企业办公自动化的核心工具,正逐渐成为企业高效办公的新标配。一、OA系统的重要性OA系统,…...
窗口总乱跑?PersistentWindows让你的桌面布局稳如泰山
窗口总乱跑?PersistentWindows让你的桌面布局稳如泰山 【免费下载链接】PersistentWindows fork of http://www.ninjacrab.com/persistent-windows/ with windows 10 update 项目地址: https://gitcode.com/gh_mirrors/pe/PersistentWindows 多显示器用户和远…...
JienDa聊PHP:ThinkPHP 8.0 企业级API开发与性能调优实战
1. ThinkPHP 8.0企业级API开发基础 ThinkPHP 8.0作为现代化PHP框架的代表,在企业级API开发领域展现出强大的优势。我最近刚用TP8完成了一个日活50万的电商平台API重构,实测下来性能提升非常明显。相比传统开发方式,TP8的API开发流程更加规范…...
抽象推理终极指南:10个ARC经典案例解析助你掌握核心技巧
抽象推理终极指南:10个ARC经典案例解析助你掌握核心技巧 【免费下载链接】ARC-AGI The Abstraction and Reasoning Corpus 项目地址: https://gitcode.com/GitHub_Trending/ar/ARC-AGI 抽象与推理语料库(ARC)是一个专门用于评估通用人…...
Alpamayo-R1-10B参数详解:Top-p=0.98与Temperature=0.6组合的工程意义解析
Alpamayo-R1-10B参数详解:Top-p0.98与Temperature0.6组合的工程意义解析 1. 项目背景与技术架构 1.1 Alpamayo-R1-10B核心定位 Alpamayo-R1-10B是NVIDIA开发的自动驾驶专用视觉-语言-动作(VLA)模型,其核心设计目标是通过类人因果推理能力提升自动驾驶…...
