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

Java复习之范型相关 类型擦除

目录

是什么意思

上下届限定符

List和 List 的区别

List和 List 的区别

类型擦除

泛型与编译的关系

一、先明确:泛型的“编译时核心作用”

二、List和 List 在编译时的差异

1. 类型检查规则:编译器如何判断“操作是否合法”?

(1)List的编译检查:明确允许“所有 Object 及其子类”

(2)List 的编译检查:“未知类型”导致的严格限制

2. 类型擦除后的差异:编译后泛型信息如何消失?

三、总结:编译时差异的核心

四、擦除前后的变化

1. 的擦除与逻辑体现

2. 的擦除与逻辑体现

五、总结:关键结论


<T>是什么意思

这是一种类型参数

用于定义通用类型 这是一种约定 本质上我们可以指定具体的类型来代替 T

定义 Box 类

// 泛型类,T 是类型参数
class Box<T> {private T content; // 用 T 定义变量类型public void setContent(T content) { // 用 T 定义方法参数类型this.content = content;}public T getContent() { // 用 T 定义返回值类型return content;}
}

验证

// T 被替换为 String,盒子只能存字符串
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello"); 
String str = stringBox.getContent(); // 无需强制类型转换// T 被替换为 Integer,盒子只能存整数
Box<Integer> intBox = new Box<>();
intBox.setContent(123);
int num = intBox.getContent();

上下届限定符

< ? extends T > 指的是 上界通配符 指的是泛型中的类必须为当前类或为当前类的子类

< ? super T> 指的是 下界通配符 指的是泛型中的类必须为当前类或为当前类的父类

List<Object> 和 List<?>的区别

List<?> 是一个未知类型的 List,而 List<Object> 其实是任意类型的 List。

你可以把 List<String>, List<Integer>赋值给 List<?>,却不能把 List<String>赋值给 List<Object>。

List<Object> 和 List 的区别

List 叫原始类型

在编译的时候 编译器不会对原始类型进行类型安全检查 却会对带参数的类型进行检查

通过 Object 作为类型 我们可以告诉编译器该方法可以接受任意类型的对象

你可以把任何带参数的类型传递给原始类型 List,但却不能 把 List<String>传递给接受 List<Object>的方法,因为会产生编译错误。

类型擦除

< ? extends T > 指的是 上界通配符 指的是范型中的类必须为当前类或为当前类的子类

泛型与编译的关系

Java 泛型是一个编译器层面存在的语法糖 所有逻辑只会在编译期间完成 运行时不会保留泛型信息

要理解泛型在编译时的差异(尤其是 List<Object>List<?>),核心在于 编译器如何处理类型信息。Java 泛型的核心机制是 类型擦除(Type Erasure),但擦除前后的编译检查逻辑才是差异的关键。

一、先明确:泛型的“编译时核心作用”

Java 泛型本质是 编译器的语法糖,它的所有逻辑(类型检查、转换)都在 编译阶段 完成,运行时不会保留泛型参数信息(类型擦除)。
编译器的核心任务是:在编译时确保“泛型操作的类型安全”,避免运行时出现 ClassCastException

二、List<Object>List<?> 在编译时的差异

我们从 类型检查规则类型擦除后的结果 两个角度对比:

1. 类型检查规则:编译器如何判断“操作是否合法”?

编译器对 List<Object>List<?> 的检查逻辑完全不同,这直接导致了两者的使用限制差异。

(1)List<Object> 的编译检查:明确允许“所有 Object 及其子类”

List<Object> 中的泛型参数 <Object> 是一个 明确的类型,编译器知道:

  • 这个集合“声明为存放 Object 类型”(由于所有类都是 Object 的子类,所以实际可以放任何对象)。
  • 因此,对 List<Object> 的操作,只要符合“元素是 Object 或其子类”,就被允许。

具体表现

  • 添加元素:可以添加任何类型(因为任何对象都能向上转型为 Object)。
    编译器会检查添加的元素是否能转型为 Object(显然都能),所以允许:
List<Object> objList = new ArrayList<>();
objList.add("hello"); // 合法:String → Object 转型安全
objList.add(123);    // 合法:Integer → Object 转型安全
  • 接收赋值:只能接收 List<Object> 类型。
    因为 List<String>List<Object> 没有继承关系(即使 String 是 Object 的子类,List<String> 也不是 List<Object> 的子类),编译器会阻止这种赋值:
List<String> strList = new ArrayList<>();
List<Object> objList = strList; // 编译错误:类型不兼容

(编译器会认为:List<String> 只能存 String,而 List<Object> 可以存任何对象,若允许赋值,可能导致 objList.add(123) 破坏 strList 的类型安全。)

(2)List<?> 的编译检查:“未知类型”导致的严格限制

List<?> 中的 ? 表示 未知类型(可以是任何类型,但编译器不知道具体是哪一种)。编译器为了保证类型安全,会对其操作做严格限制:

  • 添加元素:几乎禁止添加任何具体类型(除了 null)。
    因为编译器不知道 ? 代表什么类型,假设添加 String,但实际 List<?> 可能是 List<Integer>(此时添加 String 会出错);同理添加任何类型都可能不安全。因此编译器直接禁止:
List<?> anyList = new ArrayList<String>();
anyList.add("hello"); // 编译错误:无法确定 ? 是否接受 String
anyList.add(null);    // 合法:null 可以是任何类型
  • 接收赋值:可以接收任何泛型 List(如 List<String>List<Integer>)。
    因为 List<?> 表示“某种未知类型的 List”,而 List<String> 本质上是“String 类型的 List”,属于“未知类型”的一种可能,因此允许赋值:
List<String> strList = new ArrayList<>();
List<?> anyList = strList; // 合法:strList 是“某种具体类型的 List”,符合 ? 的定义
  • 获取元素:获取的元素只能被当作 Object 处理。
    因为编译器不知道 ? 是什么类型,只能确定它一定是 Object 的子类(Java 中所有类都继承 Object),所以获取元素时会自动向上转型为 Object:
List<?> anyList = new ArrayList<String>();
Object obj = anyList.get(0); // 合法:无论 ? 是什么类型,都能转成 Object
String str = anyList.get(0); // 编译错误:无法确定 ? 是 String

2. 类型擦除后的差异:编译后泛型信息如何消失?

Java 泛型在编译后会进行 类型擦除:泛型参数会被替换为“原始类型”,同时添加必要的类型转换代码。
List<Object>List<?> 擦除后的结果不同:

  • List<Object> 擦除后 → List(原始类型),但保留了“元素是 Object”的隐含信息(因为擦除前明确是 Object)。
    例如:
List<Object> list = new ArrayList<>();
list.add("hello"); // 擦除后:add(Object obj),参数自动转型为 Object
Object obj = list.get(0); // 擦除后:get() 返回 Object,无需额外转换
  • List<?> 擦除后 → 也是 List(原始类型),但由于 ? 是未知类型,编译器会在必要时插入更严格的类型转换检查。
    例如:
List<?> anyList = new ArrayList<String>();
Object obj = anyList.get(0); // 擦除后:get() 返回 Object(和上面一样)

(虽然擦除后结果相同,但编译时的检查逻辑完全不同,这才是关键。)

三、总结:编译时差异的核心

对比维度

List<Object>

List<?>

泛型含义

明确声明为“存放 Object 类型”

声明为“存放未知类型”(具体类型不确定)

编译时检查重点

确保元素是 Object 或其子类(宽松)

确保不破坏未知类型的安全性(严格)

允许添加元素

任何类型(因都能转 Object)

几乎不允许(除 null,因未知类型无法匹配)

允许接收的赋值

只能是 List<Object>

任何泛型 List(如 List<String>

获取元素的类型

可直接当作 Object 或向下转型(需显式强转)

只能当作 Object(无法确定具体类型)

简单说:List<Object> 在编译时明确“我能存任何东西”,所以操作灵活;List<?> 明确“我不知道能存什么”,所以编译器为了安全,限制了大部分修改操作。这种差异完全是编译器在编译阶段通过类型检查规则实现的,和运行时无关。泛型擦除(Type Erasure)后,<? extends T><? super T>通配符边界信息会被擦除,但编译器在编译时基于这些边界执行的类型检查逻辑,会转化为运行时的字节码指令(主要是类型转换),间接体现了通配符的约束效果。

简单说:擦除后不会“保留”通配符的边界逻辑,但编译时基于边界做的检查,会通过生成的字节码(如强制转型)在运行时产生类似“遵循边界”的效果。

四、擦除前后的变化

1. <? extends T> 的擦除与逻辑体现
  • 编译时<? extends T> 限制“只能读取元素(作为 T 类型),不能添加除 null 外的元素”(因为编译器不知道具体是 T 的哪个子类,添加元素可能破坏类型安全)。
  • 擦除后:泛型信息被移除,List<? extends T> 会被擦除为原始类型 List,但编译器会在读取元素时自动插入 (T) 强制转型,确保返回值符合 T 类型。示例:
List<? extends Number> numList = new ArrayList<Integer>();
Number n = numList.get(0); // 编译时允许(符合 extends 约束)

擦除后字节码等价于:

List numList = new ArrayList(); // 原始类型
Number n = (Number) numList.get(0); // 编译器自动添加 (Number) 转型

这里的转型本质是编译时基于 extends Number 边界的保证:既然 numList 的元素是 Number 的子类,那么转型为 Number 一定安全。

2. <? super T> 的擦除与逻辑体现
  • 编译时<? super T> 限制“可以添加 T 或其子类的元素,但读取元素时只能作为 Object 处理”(因为编译器不知道具体是 T 的哪个父类,读取时无法确定更具体的类型)。
  • 擦除后:同样被擦除为原始类型 List,但编译器会在添加元素时检查是否为 T 或其子类(编译时保证),读取时则默认返回 Object(无需转型,或由开发者显式转型)。示例:
List<? super Integer> intList = new ArrayList<Number>();
intList.add(123); // 编译时允许(123 是 Integer 类型,符合 super 约束)
Object obj = intList.get(0); // 编译时只能作为 Object 接收

擦除后字节码等价于:

List intList = new ArrayList(); // 原始类型
intList.add(123); // 编译时已检查 123 是 Integer,允许添加(无转型必要)
Object obj = intList.get(0); // 直接返回 Object(无需转型)

这里的添加操作安全,是因为编译时基于 super Integer 边界的保证intList 的元素类型是 Integer 的父类(如 Number、Object),添加 Integer 一定兼容。

五、总结:关键结论

  1. 擦除后不保留通配符本身<? extends T><? super T> 擦除后都会变成原始类型(如 List),边界信息(extends T/super T)不会保留在运行时。
  2. 编译时检查转化为运行时转型:通配符的边界逻辑主要体现在编译时的类型检查(如允许/禁止添加元素、确定读取元素的类型),这些检查会转化为运行时的强制转型指令(如 (T)),从而间接实现“遵循边界”的效果。
  3. 核心是编译时的安全保证:擦除后能“不出错”,本质是编译器在编译阶段已经通过通配符边界排除了不安全的操作(如给 <? extends T> 添加非 null 元素会编译报错),运行时的转型只是“兑现”了编译时的安全承诺。

简单说:通配符的边界逻辑是编译器的“设计规范”,擦除后通过字节码中的转型操作“落地”,最终保证运行时的类型安全。

http://www.dtcms.com/a/596759.html

相关文章:

  • android6适配繁体
  • Python | 掌握并熟悉列表、元祖、字典、集合数据类型
  • 电子电气架构 --- SOA与AUTOSAR的对比
  • 福田做商城网站建设哪家服务周到中山百度网站推广
  • 【c++】手撕单例模式线程池
  • DNS主从服务器练习
  • 云游戏平台前端技术方案
  • 当前MySQL端口: 33060,可被任意服务器访问,这可能导致MySQL被暴力破解,存在安全隐患
  • Android开发-java版学习笔记第四天
  • C#WEB 防重复提交控制
  • Linux:systemd服务之.service文件(二)
  • 24_FastMCP 2.x 中文文档之FastMCP服务端认证:构建完整的 OAuth 服务器详解
  • Linux:认识Systemd服务(一)
  • Python编程实战 - Python实用工具与库 - 爬取并存储网页数据
  • 网站建设中字样图片wordpress首页调用文章数量
  • “基于‘多模态SCA+全周期协同’的中间件开源风险治理实践”荣获OSCAR开源+安全及风险治理案例
  • BetterDisplay Pro for Mac显示器增强工具
  • 解决huggingface下载仓库时有部分大文件不能下载的问题
  • Qt键盘组合
  • Qt中的QShortcut:高效键盘快捷方式开发指南
  • c mvc制作网站开发google谷歌
  • STM32F103RCT6+STM32CubeMX+keil5(MDK-ARM)+Flymcu完成轮询方式检测按键
  • paimon实战 -- Flink 写入 Paimon 流程深度解析
  • HOT100题打卡第35天——二分查找
  • R语言 | 带重要性相关热图和贡献图如何解释?如何绘制随机森林计算结果重要性及相关性图?[学习笔记]
  • 做 专而精 的网站网站建设个人主要事迹
  • 怎么查看一个网站是谁做的注册城乡规划师备考
  • CMake开源库的编译与使用
  • GitLab CI/CD和Arbess,开源免费CI/CD工具选型指南
  • Observability:适用于 PHP 的 OpenTelemetry:EDOT PHP 加入 OpenTelemetry 项目