Java泛型在运行时被擦除,List和List均变为List,反射操作基于字节码,只识别擦除后的原始类型,故可绕过编译期检查,但取值时因强转失败抛ClassCastException。

反射能绕过泛型检查,是因为运行时根本没泛型
Java 泛型的类型信息在编译后就没了,List 和 List 到 JVM 层都是 List,元素类型统一擦除为 Object。反射操作(如 getDeclaredMethod、invoke)直接作用于字节码层面,自然看不到也用不到泛型约束——它只认擦除后的原始签名。
用反射往 List 里 add(Integer) 不报错,但取出来会 ClassCastException
这是最典型的“安全假象”。编译器拦不住反射调用,而 JVM 在 add 时只检查参数是不是 Object 子类(Integer 当然满足),但后续 get(0) 返回 Object 后,强转 String 就崩了。
- 代码示例:
List<string> list = new ArrayList(); list.add("hello"); // 反射绕过 Method add = list.getClass().getDeclaredMethod("add", Object.class); add.invoke(list, 123); // 成功! String s = list.get(0); // 编译期 OK,但运行时 ClassCastException</string> - 关键点:
add方法签名在字节码里是add(Object),反射调用完全合法 - 风险本质:编译期类型安全被绕过,错误推迟到下游使用点爆发,堆栈难追溯
getGenericReturnType / getGenericParameterTypes 能“看到”泛型?别信错觉
这些反射 API 看起来能返回 Type,比如 ParameterizedType,但它只对**显式声明的泛型结构**有效(如方法签名、父类继承关系),不是运行时“恢复”了擦除的类型。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 能拿到的情况:
public List→getData() { ... } getGenericReturnType()返回List - 拿不到的情况:
List→list = new ArrayList(); list.getClass().getGenericSuperclass()是ArrayList,不带泛型 - 陷阱:
list.getClass().getMethod("get", int.class).getGenericReturnType()返回Object,不是String—— 因为ArrayList.get()原本就声明为Object
为什么框架(如 Jackson、MyBatis)敢用反射+泛型?它们靠的是 Class 传递
真正绕过擦除限制的不是“反射本身”,而是**把 Class 对象作为参数显式传入**,让类型信息以运行时对象形式存活下来。
- 反例:
new ArrayList→ 擦除后无迹可寻() - 正例:
mapper.readValue(json, new TypeReference→ 匿名子类保留了泛型签名,可通过- >() {})
getClass().getGenericSuperclass()提取 - 更直白的写法:
fromJson(json, User.class)→ 把User.class传进去,反射创建实例时就知道该 new 哪个类 - 注意:
Class不能代表带泛型的完整类型(如List),所以复杂嵌套得靠TypeReference或ParameterizedType手动构造
真正危险的不是反射,而是误以为“反射拿到了泛型类型”就等于“运行时有类型保障”。泛型擦除是铁律,所有绕过都依赖额外信息注入——漏掉那个 Class 或 Type 参数,安全网就破了。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










