Java数据结构 (泛型第二节) 泛型擦除机制/泛型的限制/上界下界
书接上回:Java数据结构 (泛型第一节) 为什么要有泛型/泛型语法/泛型方法-CSDN博客
访问作者Github:
https://github.com/Joeysoda/Github_java/blob/main/20240908%E6%B3%9B%E5%9E%8B/src/%E6%B3%9B%E5%9E%8B.java
目录
1. 为什么要有擦除机制?
2. 类型擦除的工作原理
2.1 泛型类的类型擦除
2.2 泛型方法的类型擦除
3. 泛型的上界与下界
3.1 指定上界的泛型
4. 类型擦除带来的限制
4.1 无法创建泛型数组
4.2 无法使用 instanceof 检查泛型类型
4.3 无法创建泛型实例
1. 为什么要有擦除机制?
Java 在 1.5 版本引入了泛型,但 Java 语言的设计理念是保证向下兼容。这意味着即使引入了泛型,Java 程序仍然要能够与不使用泛型的老版本代码(比如 Java 1.4 及更早的代码)兼容。为了实现这一点,Java 的泛型设计为在编译时执行类型检查,而在运行时完全去除泛型信息。这种机制被称为类型擦除。
如果没有类型擦除机制,使用泛型的 Java 程序在运行时将需要处理更多的类型信息,这可能会导致与现有 Java 代码的兼容性问题,并且会增加运行时的复杂性。
2. 类型擦除的工作原理
泛型的类型参数 T
在编译后会被替换为其上界(Upper Bound),如果没有指定上界,则默认为 Object
。编译器会在编译时插入必要的类型转换代码,以确保类型安全。
2.1 泛型类的类型擦除
示例代码:
public class Box<T> {private T value;public void setValue(T value) {this.value = value;}public T getValue() {return value;}
}public class Main {public static void main(String[] args) {Box<String> box = new Box<>();box.setValue("Hello");String value = box.getValue();System.out.println(value); // 输出: Hello}
}
编译时类型擦除后的代码:
public class Box {private Object value; // T 被替换为 Objectpublic void setValue(Object value) {this.value = value;}public Object getValue() {return value;}
}public class Main {public static void main(String[] args) {Box box = new Box();box.setValue("Hello");String value = (String) box.getValue(); // 编译器自动插入类型转换System.out.println(value); // 输出: Hello}
}
解释:
- 在编译后的代码中,泛型类型
T
被擦除为Object
,这就是类型擦除的工作方式。 - 在运行时,
Box
类实际上只保存了Object
类型,而不是String
类型。 - 编译器自动插入了类型转换
(String) box.getValue()
,以保证类型安全。
2.2 泛型方法的类型擦除
泛型方法示例:
public static <T> void printArray(T[] array) {for (T element : array) {System.out.println(element);}
}
编译时类型擦除后的代码:
public static void printArray(Object[] array) { // T 被擦除为 Objectfor (Object element : array) {System.out.println(element);}
}
解释:
- 在泛型方法的编译后,类型参数
T
被替换为了Object
,这意味着在运行时,方法printArray
接受的参数类型就是Object[]
,而不再区分具体的泛型类型。
3. 泛型的上界与下界
当泛型类或方法定义了类型边界时,类型擦除后泛型类型会被替换为其上界。如果没有指定上界,则替换为 Object
。
3.1 指定上界的泛型
代码示例:
public class NumberBox<T extends Number> { // T 的上界是 Numberprivate T value;public void setValue(T value) {this.value = value;}public T getValue() {return value;}
}
编译后的代码:
public class NumberBox {private Number value; // T 被替换为 Numberpublic void setValue(Number value) {this.value = value;}public Number getValue() {return value;}
}
解释:
- 在编译后,泛型类型
T
被替换为了Number
,因为在定义时我们指定了T extends Number
,即T
的上界是Number
。 - 这意味着编译后的
NumberBox
类可以接受任何Number
类型的子类对象(如Integer
,Double
),但所有泛型信息在运行时都被擦除了。
4. 类型擦除带来的限制
由于泛型的类型擦除机制,Java 中的泛型有一些限制。以下是类型擦除带来的一些常见问题:
4.1 无法创建泛型数组
由于类型擦除,泛型类型在运行时被擦除为 Object
,因此 Java 不允许直接创建泛型数组。因为在运行时无法知道数组元素的具体类型。
T[] array = new T[10]; // 错误:不能创建泛型数组
解决方案: 可以通过强制类型转换来创建泛型数组,使用 Object[]
来存储泛型类型。
T[] array = (T[]) new Object[10]; // 通过类型转换创建泛型数组
但这种方式会导致编译器发出未经检查的转换警告,因为无法在运行时确定数组的实际类型。
4.2 无法使用 instanceof
检查泛型类型
由于泛型在运行时被擦除为 Object
,所以不能使用 instanceof
检查泛型类型。
错误示例:
if (obj instanceof T) { // 错误,无法进行泛型类型检查// ...
}
解决方案: 可以通过检查擦除后的类型来替代 instanceof
,比如使用泛型的上界。
if (obj instanceof Number) { // T extends Number,可以检查 Number 类型// ...
}
4.3 无法创建泛型实例
由于泛型在运行时类型被擦除为 Object
或其上界,因此不能在类中直接创建泛型类型的实例。
错误示例:
T obj = new T(); // 错误:无法实例化泛型类型
解决方案: 可以通过传递一个类的引用(Class<T>
对象)来间接创建泛型对象。
示例:
class Box<T> {private T value;public Box(Class<T> clazz) throws IllegalAccessException, InstantiationException {// 使用传递的 Class 对象创建泛型实例this.value = clazz.newInstance();}
}
面试题目:
1. 为什么 T[] ts = new T[5];
是不对的?为什么不能直接创建泛型数组?
问题:
既然在编译时泛型 T
会被替换为 Object
,为什么 T[] ts = new T[5];
不能写成类似于 Object[] ts = new Object[5];
?
答案解析:
泛型的类型擦除机制并不能直接与数组创建的机制兼容,因为数组在 Java 中具有 "协变性"(covariant)。这意味着,如果 Integer[]
是 Object[]
的子类型,那么可以将 Integer[]
赋值给 Object[]
类型的变量。这会导致潜在的类型安全问题,尤其是在数组的运行时类型检查过程中,泛型类型擦除与数组的运行时行为产生了冲突。
解决方案:
可以通过创建 Object[]
数组并进行类型转换的方式间接创建泛型数组,但这会发出未经检查的类型转换警告。
-
数组的运行时类型检查:数组在 Java 中保留其类型信息,并且能够在运行时进行类型检查。如果尝试在运行时向
Object[]
数组中插入不兼容的元素,Java 会抛出ArrayStoreException
,因为数组知道自己是存储哪种类型的对象的。例如:
Object[] objects = new Integer[5]; objects[0] = "Hello"; // 运行时会抛出 ArrayStoreException
-
泛型的类型擦除:Java 的泛型在编译时擦除类型信息,所以如果我们能够创建
T[] ts = new T[5];
,编译器无法确保数组的类型安全,因为擦除后T
被替换为了Object
。这意味着我们无法在运行时对数组的元素类型进行检查,可能会导致数组元素的类型与数组声明的类型不匹配。
具体原因总结:
- 数组需要在运行时保留类型信息,而泛型在运行时会被擦除为
Object
,因此创建泛型数组无法保证类型安全。 - 如果允许
T[]
创建,会导致运行时无法检查数组的实际类型,可能会导致ArrayStoreException
。
解决方案:
可以通过创建 Object[]
数组并进行类型转换的方式间接创建泛型数组,但这会发出未经检查的类型转换警告。
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[5]; // 警告:未经检查的类型转换
2. 类型擦除一定是把 T 变成 Object 吗?
问题:
在类型擦除过程中,泛型 T
是否总是被擦除为 Object
?
答案解析:
不一定。泛型 T
在类型擦除过程中,会根据类型参数的上界来进行替换。如果没有指定上界,那么 T
会被替换为 Object
;但如果有上界,T
会被替换为它的上界类型。
-
无上界的类型擦除:当
T
没有上界时,T
会被擦除为Object
。
class Box<T> {private T value;public void setValue(T value) {this.value = value;}public T getValue() {return value;}
}
经过类型擦除后:
class Box {private Object value; // T 被擦除为 Objectpublic void setValue(Object value) {this.value = value;}public Object getValue() {return value;}
}
有上界的类型擦除:如果 T
有上界,例如 T extends Number
,那么在类型擦除时,T
会被替换为 Number
。
代码示例:
class Box<T extends Number> {private T value;public void setValue(T value) {this.value = value;}public T getValue() {return value;}
}
经过类型擦除后:
class Box {private Number value; // T 被擦除为 Numberpublic void setValue(Number value) {this.value = value;}public Number getValue() {return value;}
}
总结:
- 无上界泛型
T
:擦除为Object
。 - 有上界泛型
T
:擦除为其上界类型。
相关文章:

Java数据结构 (泛型第二节) 泛型擦除机制/泛型的限制/上界下界
书接上回:Java数据结构 (泛型第一节) 为什么要有泛型/泛型语法/泛型方法-CSDN博客 访问作者Github: https://github.com/Joeysoda/Github_java/blob/main/20240908%E6%B3%9B%E5%9E%8B/src/%E6%B3%9B%E5%9E%8B.java 目录 1. 为什么要有擦除机制? 2. 类…...

数据安全标准在非结构化数据中台的遵守
在数字化转型的浪潮中,非结构化数据中台作为企业数据管理的核心枢纽,承载着海量且多样的数据资产。这些数据不仅关乎企业的运营决策,更涉及客户隐私、商业机密等敏感信息。因此,确保非结构化数据中台遵守数据安全标准,…...

探索Go语言中的Goroutine并发机制
什么是Goroutine 在Go语言中,Goroutine 是程序中最基本的并发单位。事实上,每个Go程序都会自动创建一个goroutine,那就是主goroutine,程序启动时会立即执行。Goroutine是Go语言中处理并发问题的核心工具,因此理解它的工作原理至关重要。 简而言之,Goroutine是并发执行的…...

实现实时Web应用,使用AJAX轮询、WebSocket、还是SSE呢??
文章目录 短轮询(Short Polling)长轮询(Long Polling)Comet “服务器推” (这玩意现在用的很少了,了解一下即可)WebSocket原理:方法:事件: SSE原理事件 总结 …...

3GPP协议入门——物理层基础(一)
1. 频段/带宽 NR指定了两个频率范围,FR1:通常称Sub 6GHz,也称低频5G;FR2:通常称毫米波(Millimeter Wave),也称高频5G。 2. 子载波间隔 NR中有15kHz,30kHz,6…...

关于Java数据结构中集合的一个小知识
在我们以后刷题的过程,我们会遇到一些奇怪的集合数据类型。 如下图 这里,我们以顺序表的集合类为例,我们看到上图函数的返回值类型有点奇怪,其实并不奇怪,也就是穿过去的参数类型是一个顺序表的集合类型,也…...

leetcode41. 缺失的第一个正数,原地哈希表
leetcode41. 缺失的第一个正数 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1: 输入:nums [1,2,0] 输出:3 解释…...

如何准备教师资格证科目三“学科知识与教学能力”的考试与面试?(理科导向:数学/物理)
如何准备教师资格证科目三“学科知识与教学能力”的考试与面试?(理科导向:数学/物理) 目录 收起 1 前言 1.1 自身经历 1.2 教师资格证的作用 2 知识点题型分数的分布与学习建议 2.1 科目三的知识点分数分布: …...

3.数据类型
作业系统链接 Python 是一门面向对象友好的语言,支持多种内置数据类型,包括整数(int)、浮点数(float)、布尔值(bool)、字符串(str)、列表(list&am…...

Xcode报错:No exact matches in reference to static method ‘buildExpression‘
Xcode报错1:No exact matches in reference to static method buildExpression Xcode报错2:Type () cannot conform to View 这两个报错都是因为在SwiftUI的View的Body里面使用了ForEach循环,却没有在ForEach循环闭包的内部返回视图,而是做了…...

校园安全无小事,EasyCVR视频综合管理平台助力智慧校园视频监控系统全面升级
随着信息技术的飞速发展,智慧校园作为教育信息化的重要载体,正逐步成为提升校园安全管理、优化教育资源配置、增强师生互动体验的关键手段。其中,高效、智能的视频监控系统作为智慧校园不可或缺的一部分,扮演着至关重要的角色。TS…...

通过Python代码发送量化交易信号邮件通知
量化交易利用数学模型和计算机算法来分析市场数据,并生成交易信号,本文将介绍如何使用Python编写一个简单的脚本,通过发送邮件通知量化交易信号。 开启SMTP服务 首先要在发件箱的邮件设置中,将POP3/SMPT服务开启,记录下授权密码,在本地可通过此密码登录,注意有效期和保…...

计算机毕业设计 乡村生活垃圾管理系统的设计与实现 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试
🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点…...

Qwen 2.5:阿里巴巴集团的新一代大型语言模型
Qwen 2.5:阿里巴巴集团的新一代大型语言模型 摘要: 在人工智能领域,大型语言模型(LLMs)的发展日新月异,它们在自然语言处理(NLP)和多模态任务中扮演着越来越重要的角色。阿里巴巴集…...

Element UI入门笔记(个人向)
Element UI入门笔记 将页面分割为一级菜单、二级菜单、导航栏三个部分;使用npm下载安装,使用语句npm i element-ui -s; 布局组件 el-form 用于创建和管理表单;从属性上看: :model:用于双向数据绑定,将表单…...

网络通信失败-关闭网络防火墙
0、报错描述1、分析2、解决办法 0、报错描述 在进行树莓派和PC端的网络通信的时候, 使用树莓派作为服务端,PC端作为客户端的时候,能成功通讯。 使用树莓派作为客户端,PC端作为服务端的时候,却发现通信失败。 体现在没…...

基于kolla-ansible在openEuler 22.03 SP4上部署OpenStack-2023.2
测试环境 openEuler-22.03-LTS-SP4-x86_64-dvd.iso Virtual Box,4 vCPU, 8G RAM, 50 vDisk。安装时删除/home,SWAP分区,全部空间给/目录。 目标是部署OpenStack All-In-One模式,控制节点计算节点存储节点在一台机器实现。 系统配…...

深拷贝|浅拷贝
目录 1. 深拷贝(Deep Copy) 2. 浅拷贝(Shallow Copy) 3. 深拷贝和浅拷贝的区别 4. 示例代码 浅拷贝示例 深拷贝示例 5.常用的方法 1.Java Object.clone() 方法 2.序列化与反序列化 6.Spring Boot 中的常用方法 使用 Se…...

图像处理-掩码
文章目录 一、简介二、主要用途三、代码实现四、掩码优缺点1.优点2.缺点 一、简介 在图像处理中,掩码(Mask)是一种特殊的图像,用于指定对原始图像进行操作的区域。掩码通常是二值图像(即图像上的每个像素只有两个可能…...

[2025]基于微信小程序慢性呼吸系统疾病的健康管理(源码+文档+解答)
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...

react之jsx基础(1)概念和本质
文章目录 JSX 的基本概念1. **语法**2. **表达式**3. **属性**4. **子元素** JSX 的编译过程1. **转换成 JavaScript**2. **React 元素** JSX 的实际应用1. **组件定义**2. **组件嵌套** 总结 当然,以下是对 JSX 的详细讲解,包括其基本概念、语法、编译过…...

sqli-labs靶场自动化利用工具——第13关
文章目录 概要整体架构流程技术细节执行效果小结 概要 Sqli-Labs靶场对于网安专业的学生或正在学习网安的朋友来说并不陌生,或者说已经很熟悉。那有没有朋友想过自己开发一个测试脚本能实现自动化化测试sqli-labs呢?可能有些人会说不是有sqlmap&#…...

大舍传媒:尼日利亚传统新闻媒体宣传助力新兴行业蓬勃发展
大舍传媒:尼日利亚传统新闻媒体宣传助力新兴行业蓬勃发展 在全球化的浪潮下,媒体作为信息传播的重要渠道,对于促进行业发展和推动社会进步扮演着举足轻重的角色。特别是在非洲大陆上人口最多、经济最发达的国家——尼日利亚,传统…...

ISSTA 2024盛大开幕:中国学者的录取数和投稿量均位列第一
随着夏日的尾声,全球软件测试领域的专家和学者齐聚在奥地利维也纳。共同参与这场科技盛宴——ISSTA 2024。这场国际会议正如火如荼地进行中,吸引了来自世界各地的专业人士参与。 会议实况: 9月16日与17日,大会安排了丰富的社交活…...

HttpMediaTypeNotAcceptableException: No acceptable representation问题解决方法
Background org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation HttpMediaTypeNotAcceptableException: No acceptable representation 异常通常发生在Web应用程序中,客户端请求了一个资源,但是…...

Scrapy爬虫框架 Pipeline 数据传输管道
在网络数据采集领域,Scrapy 是一个非常强大的框架,而 Pipeline 是其中不可或缺的一部分。它允许我们在数据处理的最后阶段对抓取的数据进行进一步的处理,如清洗、存储等操作。 本教程将详细介绍如何在 Scrapy 中使用 Pipeline,帮…...

vim的 配置文件
vim 的配置文件名是vimrc,共有两个,一个是公共的、所有用户的vimrc,一个是私有的、个人的.vimrc。个人的配置文件是隐藏的,不进行配置的话一般是没有这个文件的,需要自己创建.vimrc 公共配置文件位于 :/etc/vim/vimrc…...

Golang | Leetcode Golang题解之第403题青蛙过河
题目: 题解: func canCross(stones []int) bool {n : len(stones)dp : make([][]bool, n)for i : range dp {dp[i] make([]bool, n)}dp[0][0] truefor i : 1; i < n; i {if stones[i]-stones[i-1] > i {return false}}for i : 1; i < n; i {…...

前端项目使用js将dom生成图片、PDF
在进行下方操作前,请你先安装 html2canvas 和 jspdf 包。 1、使用html2canvas将dom元素生成图片 // 获取要转换的dom const ele document.getElementById("dom"); // 生成canvas对象 let canvas await html2canvas(ele); 2、生成PDF对象,将…...

在 Red Hat 上安装 SQL Server 2022 并创建数据库
适用于: SQL Server - Linux 本快速入门介绍如何在 Red Hat Enterprise Linux (RHEL) 8.x 或 9.x 上安装 SQL Server 2022 (16.x)。然后可以使用 sqlcmd 进行连接,创建第一个数据库并运行查询。 注意:本教程需要用户输入和 Internet 连接。 …...