一句话说透Java里面的类型擦除和通配符

176 阅读2分钟

一、类型擦除:泛型的「皇帝新衣」

比喻:Java的泛型在编译后就像被脱掉衣服的皇帝——代码里写的是List<String>,但运行时实际是List<Object>,类型信息被擦除了。


1. 类型擦除的本质
  • 编译时严格检查:写代码时编译器会检查类型是否匹配。

    List<String> list = new ArrayList<>();
    list.add("Hello"); // ✅ 合法
    list.add(123);     // ❌ 编译报错
    
  • 运行时裸奔:编译后的字节码中泛型类型被擦除,替换为原始类型(如Object或指定边界类型)。

    // 编译前
    List<String> list = new ArrayList<>();
    // 编译后(等效代码)
    List list = new ArrayList(); // 类型变Object
    
2. 类型擦除的限制
  • 不能创建泛型数组

    // 编译报错:不可创建泛型数组
    List<String>[] array = new ArrayList<String>[10]; 
    
  • 无法用instanceof检查类型

    if (list instanceof List<String>) {} // ❌ 编译报错
    
  • 无法直接实例化泛型对象

    T obj = new T(); // ❌ 编译报错(不知道T的具体类型)
    

二、通配符:灵活的类型边界

比喻:通配符就像一张通行证,规定谁能进(extends)或谁可以接收(super)。


1. 三种通配符
通配符含义使用场景
<?>任意类型(未知类型)只读操作(如遍历)
<? extends T>T或T的子类(上界通配符)生产者(提供数据)
<? super T>T或T的父类(下界通配符)消费者(接收数据)
2. PECS原则(Producer-Extends, Consumer-Super)
  • 生产者(Producer)用extends

    // 从集合读取数据(生产数据)
    void printAll(List<? extends Number> list) {
        for (Number num : list) {
            System.out.println(num);
        }
    }
    
    • 可传入List<Integer>List<Double>等。
  • 消费者(Consumer)用super

    // 向集合写入数据(消费数据)
    void addNumbers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
    }
    
    • 可传入List<Number>List<Object>等。

3. 通配符实战对比
场景错误写法正确写法
读取数据List<Object> 接收List<String>List<?>List<? extends Object>
写入数据List<String> 添加IntegerList<? super String>

三、类型擦除的底层真相

1. 编译后代码对比
// 源码(泛型)
public class Box<T> {
    private T value;
    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

// 编译后(类型擦除)
public class Box {
    private Object value; // T被擦除为Object
    public void set(Object value) { ... }
    public Object get() { return value; }
}
2. 边界处理(有界泛型)
// 源码(有界泛型)
public class NumberBox<T extends Number> {
    private T value;
}

// 编译后
public class NumberBox {
    private Number value; // T被擦除为Number
}

四、总结口诀

「泛型擦除是伪装,编译检查运行时忘
通配符分上下界,生产消费要分详
PECS原则记心间,灵活安全两不误!」

附:高频面试题

  1. 为什么Java要用类型擦除实现泛型?

    • 兼容性:为了兼容JDK5之前的旧代码。
  2. List<?>List<Object>有什么区别?`

    • List<?>是未知类型(只读),List<Object>是明确类型(可写任意对象)。
  3. 如何绕过类型擦除的限制?

    • 通过反射或传递Class<T>参数(如newInstance())。