Java泛型类型检查仅在编译期生效,运行时泛型信息被擦除,JVM只认擦除后的原始类型(如List)和方法签名(如add(Object)),故反射可绕过编译校验插入非法类型,导致后续强转或遍历时抛ClassCastException。

Java泛型的类型检查只在编译期生效,运行时所有泛型信息都被擦除。List
为什么反射能绕过泛型检查
关键在于 JVM 不认识泛型,只认擦除后的签名:
- ArrayList 的 add 方法在字节码中声明为
add(Object),而非add(String)或add(Integer) - 反射通过
getDeclaredMethod("add", Object.class)获取该方法,传入任意 Object 子类(如 Integer、File、自定义对象)都合法 - 编译器无法干预反射调用,JVM 也无需验证“是否符合原泛型约束”
实际操作:向 List 插入 Integer
以下代码可在运行时成功执行,但埋下后续异常隐患:
- 创建泛型集合:
List<string> list = new ArrayList();</string> - 获取擦除后的方法:
Method add = list.getClass().getMethod("add", Object.class); - 反射插入非法类型:
add.invoke(list, 42);—— 不报错,list 变成["hello", 42] - 取值时才暴露问题:
String s = list.get(1);抛ClassCastException,因 42 是 Integer,不能转 String
哪些地方会“突然崩掉”
错误不会出现在插入时刻,而是在下游使用强转或增强 for 循环时爆发:
- 用
for (String s : list)遍历时,遇到 Integer 立即抛异常 - 调用
list.stream().map(String::length)会在 map 阶段失败 - 序列化/反序列化工具(如 Jackson)若未显式传入 TypeReference,可能解析出错或静默丢数据
- IDE 和静态分析工具完全无法预警,只能靠运行时日志或单元测试捕获
安全替代方案
若业务真需运行时灵活类型,应避免依赖擦除漏洞,改用显式类型管理:
- 用
List<object></object>+ 注释说明元素约定,配合文档和测试保障一致性 - 封装带类型校验的容器:
class TypedBox<t> { private final Class<t> type; void add(Object o) { if (!type.isInstance(o)) throw new IllegalArgumentException(...); } }</t></t> - 序列化场景统一使用
TypeReference<list>> {}</list>,借匿名子类保留泛型签名 - 框架层(如 MyBatis、Spring Data)通过 Class 参数或 ParameterizedType 上下文还原类型,而非猜测

















