Skip to content
进阶

一句话答案

Java 泛型在编译期检查类型安全,编译后擦除为 Object(或上界),运行时无法获取泛型实际类型。

核心要点

泛型的本质:编译期类型检查,运行期擦除

java
// 编译前(有泛型信息)
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);   // 编译器自动插入 (String) 强转

// 编译后(类型擦除,字节码中没有泛型信息)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);   // 编译器插入的强制类型转换

类型擦除规则:

① 无界泛型 <T>        → 擦除为 Object
② 有界泛型 <T extends Number> → 擦除为 Number(上界)
③ 所有泛型类型参数在运行时消失,JVM 只看到原始类型

类型擦除带来的限制:

java
// 1. 不能 new 泛型数组
T[] arr = new T[10];              // 编译错误

// 2. 不能 instanceof 泛型
if (obj instanceof List<String>)  // 编译错误,运行时无法区分 List<String> 和 List<Integer>

// 3. 不能 new 泛型对象
T t = new T();                    // 编译错误

// 4. 静态方法/字段不能使用类的泛型参数
class Foo<T> {
    static T value;               // 编译错误
}

PECS 原则(Producer Extends, Consumer Super):

java
// Producer(读取数据)→ 用 extends(上界通配符)
// "生产"数据给我用,我只读不写
void printAll(List<? extends Number> list) {
    for (Number n : list) {       // 读取:安全,一定是 Number 或其子类
        System.out.println(n);
    }
    // list.add(1);               // 写入:编译错误!不知道具体是 List<Integer> 还是 List<Double>
}

// Consumer(写入数据)→ 用 super(下界通配符)
// "消费"我给的数据,我只写不读
void addNumbers(List<? super Integer> list) {
    list.add(1);                  // 写入:安全,一定能接受 Integer
    list.add(2);
    // Integer n = list.get(0);   // 读取:编译错误!只能读出 Object
}

PECS 经典应用(Collections.copy 签名):

java
public static <T> void copy(List<? super T> dest,    // 消费者:往里写
                             List<? extends T> src) { // 生产者:从中读
    for (int i = 0; i < src.size(); i++)
        dest.set(i, src.get(i));
}
追问与易错

追问方向:

  • 类型擦除后怎么保证类型安全?(编译期检查 + 强制类型转换由编译器插入)
  • 运行时能获取泛型类型吗?(通过反射获取泛型父类/泛型字段的 ParameterizedType)
  • 泛型数组为什么不能直接创建?(类型擦除后无法保证数组的运行时类型检查)

易错点:

  • ❌ "Java 泛型和 C++ 模板一样"——Java 是擦除式泛型,运行时无类型信息
  • ❌ 混淆 <? extends T><? super T>——PECS 原则:生产者 extends 消费者 super

💡 记忆锚点

泛型是编译期的安检门,过了门(编译后)类型标签就撕掉了(擦除为Object/上界),运行时JVM是"类型色盲"。读数据用extends(生产者),写数据用super(消费者),口诀PECS。