Java泛型类型擦除是编译期行为,运行时泛型信息被擦除为原始类型;通配符(? extends T、? super T)是在擦除前提下提供安全灵活引用的机制,遵循PECS原则:生产者用extends,消费者用super。

Java 泛型的类型擦除是编译期行为,运行时泛型信息不存在;通配符(? extends T 和 ? super T)不是用来“绕过”擦除的工具,而是**在类型擦除前提下,提供更安全、更灵活的引用方式**。它们的作用是约束泛型参数的上界或下界,让编译器能在擦除后仍做合理的类型检查。
为什么需要 ? extends T(上界通配符)
当你只从集合中读取元素,且希望接收任意 T 的子类型时使用。它保证了“读安全”——取出的元素至少是 T 类型。
-
典型场景:方法参数接受
List extends Number>,可传入List、List或List -
限制:不能向该列表添加任何对象(除了
null),因为编译器不知道具体是哪个子类型,避免破坏类型一致性 -
例子:
void printNumbers(List extends Number> list) { for (Number n : list) { // ✅ 安全:所有元素都能当 Number 用 System.out.println(n); } // list.add(42); // ❌ 编译错误:不知道具体类型,不敢加 }
为什么需要 ? super T(下界通配符)
当你主要向集合中写入 T 或其子类型对象,且希望兼容更“宽泛”的容器时使用。它保证了“写安全”——可以放心放入 T 及其子类实例。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
-
典型场景:方法参数接受
List super Integer>,可传入List、List或List -
限制:读取时只能当作
Object(最上层父类)来用,因为编译器只知道下界,不确定具体类型 -
例子:
void addIntegers(List super Integer> list) { list.add(100); // ✅ 安全:Integer 可放进 Integer/Number/Object 容器 list.add(new Integer(200)); // Number n = list.get(0); // ❌ 编译错误:get() 返回 Object,不能直接转 Number Object o = list.get(0); // ✅ 只能当 Object 处理 }
PECS 原则:记住什么时候用哪个
Joshua Bloch 提出的口诀:Producer Extends, Consumer Super(生产者用 extends,消费者用 super)。
- 如果泛型位置是数据来源(你从中取值),用
? extends T - 如果泛型位置是数据去向(你往里放值),用
? super T - 如果既读又写,通常不用通配符,直接用具体类型(如
List)更清晰安全
类型擦除与通配符的关系
通配符本身也会被擦除——List extends Number> 擦除后就是 List(原始类型)。但擦除前,编译器已根据通配符规则完成类型检查;擦除后,JVM 运行时只看到原始类型,而安全性已在编译期锁定。
- 通配符不改变擦除事实,而是让擦除前的类型系统更健壮
- 没有运行时泛型信息,所以
instanceof无法判断通配符实际类型,也不能用new ArrayList extends T>()实例化 - 通配符常用于方法签名、变量声明、参数类型,极少用于构造器或泛型类定义本身
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










