Java 泛型相关面试题¶
1. 什么是泛型?为什么需要泛型?¶
泛型(Generic)是 JDK 5 引入的参数化类型机制,把类型当作参数传递,实现:
- 编译期类型检查:把运行时的
ClassCastException提前到编译期。 - 消除强制类型转换:
List<String>取出就是String,不用强转。 - 代码复用:一套逻辑支持多种类型。
2. 类型擦除(Type Erasure)¶
Java 泛型是伪泛型:只在编译期存在,编译后字节码里统一擦除为原始类型(Object 或上界)。
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass()); // true,都是 ArrayList
擦除规则:
<T>→Object<T extends Number>→Number<T extends Serializable & Comparable>→Serializable(第一个上界)
3. 通配符:? extends T / ? super T¶
? extends T:上界通配符,读取安全(读出来是 T),不能写入(除了 null)。生产者。? super T:下界通配符,写入安全(可以写 T 及其子类),读出来只能当 Object。消费者。
PECS 原则:Producer-Extends, Consumer-Super。
4. 常见限制¶
- 不能 new T():
new T()擦除后变成new Object(),语义错误。解决:传Class<T>。 - 不能 new T[]:数组不擦除,要用
(T[]) Array.newInstance(...)或List<T>代替。 - 基本类型不能作泛型参数:
List<int>非法,要用List<Integer>。 - 静态字段不能引用类的类型参数:static 属于类,T 属于实例。
- 不能 catch 泛型异常,但可以
throws T(受限)。 instanceof List<String>非法,只能instanceof List<?>。
5. 桥接方法(Bridge Method)¶
class Parent<T> {
void set(T t) {}
}
class Child extends Parent<String> {
@Override void set(String s) {}
}
编译后字节码会生成一个桥接方法 void set(Object t),内部转调 set(String),这是为了重写语义在擦除后仍成立。
6. 泛型数组为什么不允许¶
数组是协变且运行时保留元素类型的,而泛型擦除后无法在运行时检查元素类型,可能导致堆污染(Heap Pollution)。
高频追问
- 什么是堆污染?把
List<String>赋值给List<Object>(未经检查的转换)导致运行时类型不一致。 - 为什么
List<String>不是List<Object>的子类?因为泛型不协变,要用List<? extends Object>。