泛型擦除导致运行时ClassCastException,因编译器隐式插入强制转换;List<String>与List<Integer>擦除后均为List,JVM无法区分,赋值不报错但取值调用时崩。

泛型擦除本身不抛异常,但它让编译器在你没写强制转换的地方,悄悄插进一句 (String)、(Number) 这样的转换——等真正执行到那行时,对象类型对不上,ClassCastException 就突然爆发。这种错误不发生在赋值时,而发生在取值或调用方法的那一刻,非常隐蔽。
擦除后“类型一致”的假象
运行时 List<String> 和 List<Integer> 都是 List,JVM 看不出区别。所以你可以把一个装了 Integer 的原始 ArrayList 直接赋给 List<String> 变量,编译器只给警告,不报错。但只要你调用 strList.get(0).length(),字节码里早已埋好的 (String) 转换就会尝试把 Integer 强转成 String,立刻崩。
隐式转换藏在哪儿?
这些 cast 不是你写的,是编译器自动加的,常见位置包括:
- 从泛型集合取元素:如
list.get(i)→ 编译后变成(T) list.get(i) - 泛型方法返回值:如
<T> T getValue()调用后被转为具体类型 - 泛型字段读取:如
box.get()返回前会插入对应类型的 cast - 反射调用后强转:比如
(List<User>) method.invoke(obj),实际返回可能是ArrayList<LinkedHashMap>
典型失配源头
以下操作看似无害,实则为运行时异常埋雷:
- 用原始类型(
List raw = new ArrayList())添加异构对象,再转成参数化类型 - JSON 反序列化未传
TypeReference<List<User>>,默认得到List<Object>或List<LinkedHashMap> - MyBatis 用
Map<String, String>接收数据库BIGINT字段,取值时转String不报错,但调用get("id").length()会因实际是BigInteger而失败 - 泛型工厂方法写成
return (List<T>) new ArrayList(),T 在运行时是Object,后续任何强转都不可靠
怎么提前发现?
别等崩溃才查。重点关注三点:
- 异常消息里 “cannot be cast to” 前后的类型,就是擦除后暴露的真实失配点
- 堆栈最上层那行“安全”代码(比如
list.get(0).toString()),它背后藏着编译器插入的 cast - 顺藤摸瓜检查该变量的初始化路径:是否经过原始类型、反射、序列化、或跨模块传参?这些环节最容易丢失泛型信息

















