跳转至

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

// 读 List<? extends Number>:可以 Number n = list.get(i)
// 写 List<? super Integer>:可以 list.add(1)

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. 泛型数组为什么不允许

List<String>[] arr = new List<String>[10]; // 编译错误

数组是协变运行时保留元素类型的,而泛型擦除后无法在运行时检查元素类型,可能导致堆污染(Heap Pollution)。

高频追问

  • 什么是堆污染?把 List<String> 赋值给 List<Object>(未经检查的转换)导致运行时类型不一致。
  • 为什么 List<String> 不是 List<Object> 的子类?因为泛型不协变,要用 List<? extends Object>