Java 泛型是怎么擦除的?桥接方法又是什么?

结论先行:Java 泛型是编译期语法糖:类型参数在编译后被“擦除”(Type Erasure)成上限类型(无界就是 Object),字节码中没有泛型信息,所以运行时 List<String> 与 List<Integer> 是同一个 List 类。桥接方法(bridge method)是编译器在“泛型父类方法 + 子类覆写导致签名不一致”时自动生成的合成方法:它把 Object 形态的参数转成具体类型再转发,保证通过父类引用调用时多态仍然正确。

擦除规则速记

  • 无界类型参数 <T> → Object;
  • 有界类型参数 <T extends Comparable<T>> → Comparable;
  • 泛型类/泛型方法的类型参数在字节码中消失;
  • 取值处的强制转换由编译器自动插入(checkcast 指令)。

桥接方法示例

class Box<T> {
    public T get() { return null; }        // 擦除后签名:Object get()
}
class StringBox extends Box<String> {
    @Override
    public String get() { return "s"; }    // 子类签名:String get()
}
// 编译器自动补一个合成桥接:
//   public Object get() { return this.get(); }  ← 两套签名共存,多态才成立

没有桥接方法时,父类引用调用 get() 只会命中 Object get() 的虚方法分派,子类的 String get() 因签名不同形同“重载”而非“覆写”,多态就错了。

擦除带来的反直觉现象

  • List<String> 与 List<Integer> 的 getClass() 是同一个 List.class;
  • 泛型数组不合法:new T[] 编译不过,只能 (T[]) new Object[] 并接受检查告警;
  • 泛型不能出现在异常捕获(catch T 不合法),因为运行时类型已不存在;
  • 只有泛型参数不同的方法签名视为重复,无法靠泛型做重载。

用反射验证擦除

List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass()); // true:运行时是同一个 ArrayList

常见追问 / 记忆点

  • 追问:运行时能拿到泛型信息吗?答:类声明处的泛型可用 Type/ParameterizedType 反射拿到;单个集合实例的元素类型拿不到(已被擦除)。
  • 追问:为什么说泛型不能重载?答:List<String> 与 List<Integer> 擦除后都是 List,签名相同,编译器视为重复定义。
  • 记忆点:泛型“编译期存在、运行时擦除”;看到栈里出现 bridge method,那是编译器为多态补的转接头,不是业务 bug。
笔记加载中…