当前位置: 首页 > news >正文

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[] 数组并进行类型转换的方式间接创建泛型数组,但这会发出未经检查的类型转换警告

  1. 数组的运行时类型检查:数组在 Java 中保留其类型信息,并且能够在运行时进行类型检查。如果尝试在运行时向 Object[] 数组中插入不兼容的元素,Java 会抛出 ArrayStoreException,因为数组知道自己是存储哪种类型的对象的。

    例如:

    Object[] objects = new Integer[5];
    objects[0] = "Hello";  // 运行时会抛出 ArrayStoreException
    

  2. 泛型的类型擦除: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 会被替换为它的上界类型。

  1. 无上界的类型擦除:当 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数据结构 (泛型第二节) 泛型擦除机制/泛型的限制/上界下界

书接上回&#xff1a;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. 为什么要有擦除机制&#xff1f; 2. 类…...

数据安全标准在非结构化数据中台的遵守

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

探索Go语言中的Goroutine并发机制

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

实现实时Web应用,使用AJAX轮询、WebSocket、还是SSE呢??

文章目录 短轮询&#xff08;Short Polling&#xff09;长轮询&#xff08;Long Polling&#xff09;Comet “服务器推” &#xff08;这玩意现在用的很少了&#xff0c;了解一下即可&#xff09;WebSocket原理&#xff1a;方法&#xff1a;事件&#xff1a; SSE原理事件 总结 …...

3GPP协议入门——物理层基础(一)

1. 频段/带宽 NR指定了两个频率范围&#xff0c;FR1&#xff1a;通常称Sub 6GHz&#xff0c;也称低频5G&#xff1b;FR2&#xff1a;通常称毫米波&#xff08;Millimeter Wave&#xff09;&#xff0c;也称高频5G。 2. 子载波间隔 NR中有15kHz&#xff0c;30kHz&#xff0c;6…...

关于Java数据结构中集合的一个小知识

在我们以后刷题的过程&#xff0c;我们会遇到一些奇怪的集合数据类型。 如下图 这里&#xff0c;我们以顺序表的集合类为例&#xff0c;我们看到上图函数的返回值类型有点奇怪&#xff0c;其实并不奇怪&#xff0c;也就是穿过去的参数类型是一个顺序表的集合类型&#xff0c;也…...

leetcode41. 缺失的第一个正数,原地哈希表

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

如何准备教师资格证科目三“学科知识与教学能力”的考试与面试?(理科导向:数学/物理)

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

3.数据类型

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

Xcode报错:No exact matches in reference to static method ‘buildExpression‘

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

校园安全无小事,EasyCVR视频综合管理平台助力智慧校园视频监控系统全面升级

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

通过Python代码发送量化交易信号邮件通知

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

计算机毕业设计 乡村生活垃圾管理系统的设计与实现 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…...

Qwen 2.5:阿里巴巴集团的新一代大型语言模型

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

Element UI入门笔记(个人向)

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

网络通信失败-关闭网络防火墙

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

基于kolla-ansible在openEuler 22.03 SP4上部署OpenStack-2023.2

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

深拷贝|浅拷贝

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

图像处理-掩码

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

[2025]基于微信小程序慢性呼吸系统疾病的健康管理(源码+文档+解答)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

OPENCV形态学基础之二腐蚀

一.腐蚀的原理 (图1) 数学表达式&#xff1a;dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一&#xff0c;腐蚀跟膨胀属于反向操作&#xff0c;膨胀是把图像图像变大&#xff0c;而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...