八股-泛型
泛型
1. 基础概念
定义:泛型是 JDK 5 引入的特性,允许在定义类、接口、方法时使用类型参数(Type Parameters),使用时再用具体类型替换。
本质:参数化类型(Parameterized Types),将数据类型作为参数传递。
2. 使用泛型的核心优势
2.1 编译期类型安全(Type Safety)
问题背景:JDK 5 之前,集合使用 Object 存储元素,取出时需强制类型转换,运行时易发生 ClassCastException。
1 | // 无泛型时代码(错误示例) |
泛型解决方案:
1 | // 使用泛型 |
关键点:类型检查从运行时提前到编译时,符合”Fail Fast”原则。
2.2 代码复用与算法抽象
编写与类型无关的通用逻辑,避免为不同数据类型重复写相似代码。
1 | // 泛型方法示例:交换任意类型的数组元素 |
3. 类型擦除(Type Erasure)
3.1 什么是类型擦除?
Java 泛型是伪泛型,编译器在编译阶段将泛型信息擦除,替换为边界类型(默认 Object,有界泛型则替换为边界),生成的字节码中不包含泛型类型信息。
编译前后对比:
1 | // 源代码 |
3.2 为什么要类型擦除?—— 向后兼容性(Backward Compatibility)
历史背景:Java 5 引入泛型时,已有大量非泛型代码运行于旧版 JVM,且不允许修改 JVM 指令集。
设计权衡:
- 目标:新泛型代码能跑在旧 JVM 上,且新旧代码能互操作
- 方案:类型擦除,仅在编译期做类型检查,运行期无泛型信息
- 代价:运行期无法获取泛型类型(如
T.class不合法)
4. 类型擦除的具体规则与影响
4.1 擦除规则
| 泛型声明 | 擦除后类型 |
|---|---|
List<T> |
List(原始类型,元素视为 Object) |
List<String> |
List |
List<T extends Number> |
List<Number> |
<T extends Comparable<T>> |
Comparable |
4.2 带来的限制
不能创建类型参数实例
new T()非法,因为编译后变为new Object()不能创建泛型数组
new T[10]非法,可能导致数组存储异常无法使用 instanceof 检查泛型
if (obj instanceof List<String>)编译错误,只能写List<?>静态变量共享
1
2
3class Test<T> {
static T value; // 编译错误,静态变量无法使用类型参数
}桥接方法(Bridge Method)
泛型类继承时,编译器会自动生成桥接方法保持多态性(了解即可)。
5. 通配符与边界(进阶)
5.1 无界通配符 <?>
表示未知类型,用于只读场景:
1 | public void printList(List<?> list) { |
5.2 上界通配符 <? extends T>
只读不写(除 null 外),适用于读取数据:
1 | // 接受 Number 及其子类(Integer, Double等) |
5.3 下界通配符 <? super T>
只写不读(读出来是 Object),适用于写入数据:
1 | // 接受 Integer 及其父类(Number, Object) |
PECS 原则:Producer-Extends, Consumer-Super(生产者用上界,消费者用下界)
6. 考点速记
| 问题 | 要点 |
|---|---|
| 泛型的好处? | 编译期类型安全 + 消除强制转换 + 代码复用 |
| 类型擦除是什么? | 编译后泛型变 Object/边界,字节码无泛型信息 |
| 为什么擦除? | 兼容 JDK 5 之前的代码和 JVM |
| List |
运行时相同(都是 List),编译期不同 |
| 如何获取泛型类型? | 运行时无法直接获取,但可通过反射获取父类/接口的泛型参数(如 getGenericSuperclass()) |
| 泛型数组为什么不行? | 数组协变 + 类型擦除 = 可能破坏类型安全 |