我的 Java 学习笔记 · 第 7 篇:泛型与通配符

12 阅读2分钟

这一篇整理《Java核心技术 卷1》第 8 章的泛型。泛型把类型信息交给编译器检查,减少集合里放错类型和取出后强制转换。

一、泛型先解决类型安全

没有泛型时,集合里常放 Object,取值后再转换;写成 ArrayList<String> 后,编译器能检查添加和读取的类型:

ArrayList<String> names = new ArrayList<>();
names.add("Java");
String first = names.get(0);

二、泛型类和泛型方法

类型变量可以定义在类上,使用时再给出具体类型:

class Pair<T> {
    private T first;
    private T second;
}

泛型方法在返回类型前声明自己的类型变量。下面的方法假设列表非空:

public static <T> T first(List<T> values) {
    return values.get(0);
}

三、类型限定

<T extends Comparable<T>> 表示 T 必须能够与同类型对象比较。即使限定的是接口,关键字仍然是 extends;多个限定用 & 连接,例如 <T extends Number & Comparable<T>>。如果有类限定,它只能有一个,并且要写在接口限定前面。

四、通配符和读写方向

Pair<Manager> 不是 Pair<Employee> 的子类型,泛型通常不会因为类的继承关系自动变成协变类型。

  • ? extends Employee 是上界:可以把读取结果当作 Employee,但不能安全地放入某个具体的 Employee 值(null 除外)。
  • ? super Manager 是下界:可以放入 Manager 及其子类;读取时只保证得到 Object。

常用口诀是 PECS:Producer Extends,Consumer Super。作为数据提供者时优先考虑 extends,作为数据接收者时优先考虑 super。

五、类型擦除

编译后的泛型参数通常会擦除为原始类型或它的上界,所以 ArrayList<String> 和 ArrayList<Integer> 运行时属于同一个泛型类。由于运行时没有完整保留具体类型参数,new T()、new T[10] 和 instanceof Pair<String> 都不允许;可以用 instanceof Pair<?> 检查是否为某种 Pair。

一句话回顾

泛型让编译器先检查类型;遇到容器边界时,按读取或写入方向选择 extends 与 super。