泛型:泛型擦除、通配符、上下界限定
一、泛型基础概述
1. 定义
泛型(Generics)是Java 5引入的特性,允许在类、接口和方法中使用类型参数(Type Parameters),将类型明确推迟到使用时才指定,实现“代码复用 + 类型安全”。
2. 核心作用
- 类型安全:编译期检查类型,避免运行期
ClassCastException。 - 代码复用:一套逻辑支持多种数据类型,无需重复编写。
- 可读性:类型参数明确了代码的意图,无需注释说明。
二、泛型擦除(Type Erasure)
1. 概念
Java泛型是编译期特性,JVM运行时无泛型概念。编译器会在编译时“擦除”所有泛型信息,将类型参数替换为具体类型(或 Object),保证与旧版JVM兼容。
2. 擦除规则
| 类型参数情况 | 擦除后替换为 | 示例 |
|---|---|---|
无界(无 extends) |
Object |
List<T> → List<Object> |
有上界(T extends X) |
上界类型 X |
List<T extends Number> → List<Number> |
多个上界(T extends X & Y) |
第一个上界类型 X |
T extends Number & Comparable → Number |
3. 擦除后的处理
- 若类型不匹配,编译器会自动插入强制类型转换(如
String s = (String) list.get(0))。 - 若泛型方法冲突,编译器会生成桥接方法(Bridge Method)保证多态。
4. 影响与限制
- 无法创建泛型数组(如
new List<String>[5]非法,因擦除后为List<Object>[],类型不安全)。 - 无法获取泛型类型的
Class对象(如list.getClass()仅返回List.class)。 - 静态变量/方法无法使用类的泛型参数(静态域在类加载时初始化,此时泛型未实例化)。
5. 代码示例
// 编译前
List<String> list = new ArrayList<>();
list.add("Java");
String s = list.get(0);
// 编译后(擦除结果)
List list = new ArrayList();
list.add("Java");
String s = (String) list.get(0); // 自动插入强转
三、通配符(Wildcard)
1. 概念
通配符 ? 用于表示“未知类型”,解决泛型“类型不变性”导致的灵活性问题(如 List<String> 不是 List<Object> 的子类)。
2. 三种通配符类型
| 通配符类型 | 语法 | 作用 | 适用场景 |
|---|---|---|---|
| 无界通配符 | List<?> |
表示“任意类型的List”,仅能读取 Object |
工具类中处理“不关心具体类型”的逻辑 |
| 上界通配符(协变) | List<? extends T> |
表示“T或T的子类的List”,只读不写 | 生产者场景(从集合读取数据) |
| 下界通配符(逆变) | List<? super T> |
表示“T或T的父类的List”,只写不读 | 消费者场景(向集合写入数据) |
3. 核心特性
- 协变(Extends):若
A extends B,则List<A>可视为List<? extends B>的“子类”(允许向上转型)。 - 逆变(Super):若
A extends B,则List<B>可视为List<? super A>的“子类”(允许向下转型)。
四、上下界限定(Bounds)
1. 概念
上下界限定用于约束类型参数的范围,分为“上界限定(extends)”和“下界限定(super)”,可单独用于类/方法的类型参数,也可与通配符结合。
2. 上界限定(extends)
语法
- 类/接口:
class MyClass<T extends Number> { ... } - 方法:
public <T extends Number> void method(T t) { ... } - 通配符:
List<? extends Number>
作用
- 限制类型参数必须是T或T的子类,保证类型安全。
- 可调用T的方法(如
Number的intValue())。
限制
- 仅能读取,不能写入(因无法确定具体子类类型,写入会破坏类型安全)。
3. 下界限定(super)
语法
- 通配符:
List<? super String>(类/方法的类型参数不支持单独用super下界,仅能与通配符结合)
作用
- 限制通配符必须是T或T的父类,允许写入T或T的子类。
- 读取时仅能得到
Object(因无法确定具体父类类型)。
4. PECS原则(最佳实践)
Producer Extends, Consumer Super
- 若集合是生产者(提供数据,读取):用
? extends T。- 若集合是消费者(接收数据,写入):用
? super T。
五、三者的关系与实际应用
1. 泛型擦除的底层影响
- 通配符和上下界限定仅在编译期生效,运行时全部擦除为原始类型。
- 桥接方法、强制类型转换是编译器为弥补擦除损失的“补偿机制”。
2. 实际开发场景
| 场景 | 技术选型 | 示例代码 |
|---|---|---|
| 工具类(不关心具体类型) | 无界通配符 List<?> |
public static void printList(List<?> list) { ... } |
| 读取集合数据(生产者) | 上界通配符 ? extends T |
public static double sum(List<? extends Number> list) { ... } |
| 写入集合数据(消费者) | 下界通配符 ? super T |
public static void addIntegers(List<? super Integer> list) { ... } |
| 限制类型参数范围 | 类/方法上界 T extends X |
class NumberContainer<T extends Number> { ... } |
六、总结
| 核心概念 | 关键作用 | 核心限制 |
|---|---|---|
| 泛型擦除 | 兼容旧版JVM,实现编译期安全 | 运行期无泛型信息,限制数组/静态域等 |
| 通配符 | 解决类型不变性,提升灵活性 | 无界通配符功能受限,需结合上下界 |
| 上下界限定 | 约束类型范围,平衡安全与灵活 | Extends只读,Super只写(PECS) |
通过“泛型擦除保证兼容性,通配符+上下界保证灵活性”,Java泛型实现了“安全、复用、灵活”的统一。