堆污染本质是运行时泛型容器存入了不符声明类型的数据,因泛型擦除与数组协变叠加导致:可变参数转Object[]后可写入异构泛型实例,引发ClassCastException。

堆污染(Heap Pollution)在 Java 中,本质是**运行时堆中某个泛型容器实际存了不符合其声明类型的数据**,而编译器因类型擦除无法拦截——尤其在泛型与数组混用时,风险被显著放大。
泛型擦除 + 数组协变 = 污染温床
Java 泛型在编译后会被擦除(如 List<String> 变成原始 List),但数组保留运行时类型信息,且支持协变(String[] 是 Object[] 的子类型)。这两者叠加,就打开了堆污染的缺口:
-
可变参数底层是数组:声明
void foo(List<String>... lists),编译后实际接收的是List[],但 JVM 运行时只认List[],不区分泛型参数 -
数组赋值允许向上转型:你可以把
List<String>[]安全转为Object[](因为数组协变) -
Object[] 能塞进任意对象:一旦转成
Object[],就能往里写Arrays.asList(42)(即List<Integer>),而编译器和数组运行时检查都放行——因为List<Integer>也是List,符合List[]的运行时类型 -
污染完成:原
List<String>[]数组里 now 混入了List<Integer>,后续调用lists[0].get(0)就会抛ClassCastException
典型污染链路示例
以下代码看似合法,却已埋下崩溃隐患:
static void dangerous(List<String>... lists) {
Object[] objArray = lists; // ✅ 合法:List<String>[] → Object[]
objArray[0] = Arrays.asList(123); // ✅ 合法:List<Integer> 是 List,满足运行时类型
}
// 调用
dangerous(new ArrayList<String>());
// 后续使用
String s = lists[0].get(0); // ❌ 运行时 ClassCastException
为什么编译器拦不住?
关键在于两层“失守”:
立即学习“Java免费学习笔记(深入)”;
-
编译期失守:泛型擦除后,
List<String>[]和List<Integer>[]在字节码里都是List[],类型系统无法区分 -
运行时失守:数组只检查元素是否为
List(协变允许),不检查泛型实参;而泛型本身无运行时类型,无法做二次校验
如何避免这类混用风险?
核心思路是**切断泛型与数组的隐式转换通道**:
- 避免直接操作 varargs 数组:不转成
Object[],不修改、不返回、不存入外部结构 - 真需构造泛型集合时,用安全替代方案:
Arrays.asList(items)(返回不可变List<T>)、Stream.of(items).toList() - 必须返回数组?改用显式类型参数:
<T> T[] toArray(Class<T> clazz, int len),靠Array.newInstance()保证类型安全 - 加
@SafeVarargs前务必确认:方法是static或final,且体内没对items数组做任何写操作或暴露引用


















