在没有泛型的时代,Java 集合只能以 Object 为通用载体:
List list = new ArrayList();
list.add("hello");
Integer i = (Integer) list.get(0); // 运行时异常
问题的本质是:
类型信息丢失 + 类型检查延后
错误只能在运行时暴露,而不是在编译期被发现。
泛型的本质:类型参数化(Parameterized Type)
泛型不是一种新功能,而是对类型系统的扩展:
泛型的价值可以归纳为三个维度:
| 维度 | 本质 |
|---|---|
| 类型安全 | 将运行时错误提前到编译期 |
| 表达能力 | 明确数据结构中的类型意图 |
| 复用能力 | 让同一套代码服务于多种类型 |
Java 泛型可以抽象为两个基本要素:
泛型 = 类型参数化 + 类型约束
泛型
│
├── 类型参数化
│ ├── 泛型类
│ ├── 泛型接口
│ └── 泛型方法
│
├── 类型约束
│ ├── 上界约束 extends
│ ├── 下界约束 super
│ └── 通配符 ?
│
├── 使用原则
│ └── PESC 原则
│
└── 实现机制
└── 泛型擦除
泛型类的本质:
把“类的类型信息”参数化
class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T get() {
return value;
}
}
在这个结构中:
泛型方法的本质:
把“方法的类型信息”参数化
public static <T> T identity(T obj) {
return obj;
}
这里:
与泛型类类似,接口也可以参数化:
interface Converter<F, T> {
T convert(F from);
}
这三种形态本质一致:
| 形态 | 抽象层级 |
|---|---|
| 泛型类 | 对数据结构参数化 |
| 泛型接口 | 对行为参数化 |
| 泛型方法 | 对单个逻辑参数化 |
单纯的类型参数化过于自由,因此需要约束。
<T extends Comparable>
含义:
T 必须是 Comparable 的子类
可以指定多个接口:
<T extends Comparable & Serializable>
通配符本质是:
对“泛型类型参数”的一种灵活表达
<? extends T>
语义:
接受 T 或 T 的子类型
<? super T>
语义:
接受 T 或 T 的父类型
<?>
语义:
不关心具体类型
Producer Extends Consumer Super
这个原则并不是一个“经验口诀”,而是类型系统约束的必然结果。
List<? extends Fruit> basket;
特点:
本质原因:
编译器无法确定具体子类型
List<? super Apple> basket;
特点:
本质原因:
只能保证是 Apple 的父类,但不知道具体是哪一层
PESC 的真正含义是:
| 场景 | 选择 |
|---|---|
| 主要用于读取 | ? extends T |
| 主要用于写入 | ? super T |
Java API 中的经典例子:
public static <T> void copy(
List<? super T> dest,
List<? extends T> src
)
设计意图极为清晰:
JVM 并不真正理解泛型
在虚拟机层面:
编译阶段:
本质原因:
历史兼容性
由于擦除:
泛型问题本质是一个类型系统问题。
设:
A ≤ B ⇒ f(A) ≤ f(B)
A ≤ B ⇒ f(B) ≤ f(A)
f(A) 与 f(B) 无关
| 形态 | 类型关系 |
|---|---|
| List |
不变 |
| ? extends T | 协变 |
| ? super T | 逆变 |
我们可以把 Java 泛型总结为:
一套在“静态类型系统约束”与“历史兼容性”之间权衡的设计
| 层次 | 含义 |
|---|---|
| 语法层 | <T>、extends、super |
| 语义层 | 类型参数化与类型约束 |
| 实现层 | 类型擦除与桥方法 |