上一篇我们聊了泛型的基础——怎么用。 今天,我们来揭开泛型的底层原理。
你有没有想过:为什么 Java 的泛型不能用于 new T() 或判断 if (obj instanceof T)?
答案就藏在 Java 泛型的核心机制中:类型擦除(Type Erasure)。
简单说:泛型只存在于编译期,运行时会被“擦掉”。
Java 的泛型是通过“类型擦除”实现的,也就是说:
List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 问:这两个 List 的运行时类型一样吗?
System.out.println(strList.getClass() == intList.getClass()); // 输出:true你没看错!输出是 true!
因为在运行时,List<String> 和 List<Integer> 都变成了 List,泛型信息被擦除了。
<T>),擦除后变成 Object。<T extends Number>),擦除后变成上限类型(这里是 Number)。public class Box<T extends Number> {
private T value;
public T getValue() { return value; }
}编译后相当于:
public class Box {
private Number value; // T 被擦除为 Number
public Number getValue() { return value; }
}这是 Java 为了兼容老版本(Java 5 之前没有泛型)而做的设计。它让泛型代码可以和非泛型代码共存,但代价是:
new T()instanceof T? extends T 和 ? super T泛型中有一个非常重要的概念:通配符(Wildcard),尤其是它的上下限用法。
? extends T —— 上限通配符(Upper Bounded Wildcard)表示“某种类型,它是 T 或 T 的子类”。
List<? extends Number> list;这个 list 可以是:
List<Number>List<Integer>List<Double>但你不能往里面添加元素(除了 null),因为编译器不知道具体是哪种子类型。
list.add(new Integer(1)); // ❌ 编译错误!
Number n = list.get(0); // ✅ 可以读取,因为一定是 Number 或其子类👉 适用场景:只读操作(Producer)
? super T —— 下限通配符(Lower Bounded Wildcard)表示“某种类型,它是 T 或 T 的父类”。
List<? super Integer> list;这个 list 可以是:
List<Integer>List<Number>List<Object>你可以往里面添加 Integer 或其子类:
list.add(new Integer(1)); // ✅ 可以添加
Object obj = list.get(0); // ❌ 只能拿到 Object,类型信息丢失👉 适用场景:只写操作(Consumer)
记不住什么时候用 extends,什么时候用 super?记住这个口诀:
PECS = Producer-Extends, Consumer-Super
? extends T? super TCollections.copy()public static <T> void copy(List<? super T> dest, List<? extends T> src)src 是“生产者” → 用 ? extends T(从里面读 T 类型的数据)dest 是“消费者” → 用 ? super T(往里面写 T 类型的数据)完美符合 PECS!
由于类型擦除,Java 泛型有一些“坑”:
限制 | 说明 |
|---|---|
❌ 不能创建泛型数组 | new T[] 不合法,因为运行时不知道 T 是什么 |
❌ 不能用于基本类型 | List<int> 不行,要用 List<Integer> |
❌ 不能用于 instanceof | obj instanceof List<String> 不合法 |
❌ 静态变量不能使用泛型类型 | private static T t; 不合法,因为静态变量属于类,而泛型是实例相关的 |
A:Java 泛型是通过“类型擦除”实现的,泛型信息只在编译期存在,运行时会被擦除。因此它被称为“伪泛型”,与 C# 的“真泛型”不同。
List<Object> 和 List<String> 有继承关系吗?A:没有!
List<String>不是List<Object>的子类。这是泛型的“不可变性”。如果需要兼容,要用通配符,比如List<? extends Object>。
? extends T 和 ? super T 有什么区别?什么时候用?A:
? extends T表示 T 及其子类,适合“读取”;? super T表示 T 及其父类,适合“写入”。遵循 PECS 原则:生产者用extends,消费者用super。
概念 | 关键点 |
|---|---|
类型擦除 | 泛型只在编译期存在,运行时被擦除为 Object 或上限类型 |
通配符 | ? 表示未知类型,extends 是上限,super 是下限 |
PECS 原则 | 生产者用 extends,消费者用 super |
局限性 | 不能 new T、不能用基本类型、不能 instanceof 等 |
Java 泛型看似简单,实则暗藏玄机。理解了类型擦除和PECS 原则,你就掌握了泛型的“内功心法”。
下次面试官再问:“说说泛型的原理”,你就可以从容不迫地说:
“Java 泛型基于类型擦除实现,编译期检查类型安全,运行时擦除泛型信息。为了灵活处理类型兼容,我们使用
? extends T和? super T,并遵循 PECS 原则……”