Java泛型在运行时被类型擦除,List<String>和List<Integer>均变为原始List类型,JVM不校验泛型;通过反射调用add方法或操作elementData数组可绕过编译检查插入异构对象,但取值时强转会触发ClassCastException。

Java 的泛型是编译期机制,运行时会被擦除(类型擦除),所以可以通过反射操作底层的 Object[] 数组来绕过编译检查,向看似 List<String> 的列表中添加 Integer。但要注意:这会破坏类型安全,可能在后续取值时触发 ClassCastException。
利用反射获取并修改底层数组
大多数 ArrayList(包括 Arrays.asList() 返回的)内部都用 Object[] 存储元素。可通过反射访问其 elementData 字段:
- 获取
elementData字段(private Object[]),设为可访问 - 读取当前数组,直接往指定索引位置赋值
new Integer(123) - 如果数组已满,还需调用
ensureCapacityInternal或手动扩容(较复杂,通常先 add 正常元素占位)
更简单的方式:用反射调用 add 方法(忽略泛型)
编译器对 list.add(...) 的泛型检查只发生在编译期。反射调用时,JVM 不做泛型校验:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 获取
List实例的add方法(参数类型为Object) - 用
Method.invoke(list, 42)直接传入Integer,成功添加 - 此时
list.get(0)返回Integer,但若按String强转就会抛异常
实际例子(可运行)
⚠️ 仅用于理解机制,生产环境严禁使用
立即学习“Java免费学习笔记(深入)”;
List<String> list = new ArrayList<>();
list.add("hello");
// 方式一:反射调用 add
try {
Method add = list.getClass().getMethod("add", Object.class);
add.invoke(list, 123); // 成功加入 Integer
System.out.println(list); // [hello, 123]
System.out.println(list.get(1).getClass()); // class java.lang.Integer
// String s = (String) list.get(1); // ClassCastException!
} catch (Exception e) {
e.printStackTrace();
}
为什么能绕过?关键在类型擦除
泛型信息在编译后全部消失:List<String> 和 List<Integer> 运行时都是 List(原始类型)。JVM 看不到泛型约束,反射又跳过了 javac 的语法检查,所以只要底层存储结构允许(比如 Object[]),就能塞任意引用类型。

















