Java泛型数组存在根本性安全隐患,因类型擦除与数组协变性冲突;应优先用List替代,必要时用Object[]加运行时校验,公共API禁用泛型数组参数。

Java 泛型数组本身存在根本性安全隐患,原因在于泛型的类型擦除机制与数组的协变性天然冲突。编译器无法在运行时验证 T[] 中每个元素是否真正属于 T,一旦绕过检查(如用 (T[])new Object[n]),就可能在后续赋值时触发 ArrayStoreException,或在外部误用时引发 ClassCastException。真正有效的做法不是“安全地创建泛型数组”,而是从设计源头避开它。
优先用 List 替代 T[] 参数
数组只是容器的一种实现,而泛型集合才是 Java 类型安全的首选载体。几乎所有需要接收多个同类型参数的场景,都更适合用 List<t></t>。
- 把方法签名从
<T> void doSomething(T[] items)改为<T> void doSomething(List<T> items) - 调用方可直接传
Arrays.asList(a, b, c)或new ArrayList<>(Arrays.asList(...)) - 若需保持旧有调用习惯,可提供一个重载方法:接受可变参数
T...,内部立即转为Arrays.asList(...)再委托给主逻辑
只读且封闭场景下谨慎使用 @SafeVarargs
该注解不是万能开关,仅适用于满足全部条件的方法:声明为 static 或 final、不修改 varargs 数组、不将数组引用泄露给外部(例如不存入字段、不返回、不传给其他方法)。
- ✅ 安全示例:
private static <T> String formatAll(T... values) { return Arrays.toString(values); }→ 可加@SafeVarargs - ❌ 危险示例:
public <T> void cacheFirst(T... items) { this.cache = items; }→ 绝对不可加,数组被长期持有 - 注意:实例方法未声明
final、构造器、非静态方法加此注解会编译失败
必须用数组时,改用 Object[] + 运行时校验
当对接 JNI、反射、底层框架等强依赖原始数组的场景,放弃 T[] 声明,改用 Object[] 接收,并在方法入口显式校验每个元素。
立即学习“Java免费学习笔记(深入)”;
- 方法签名写成:
<T> void process(Object[] raw, Class<T> elementType) - 校验逻辑:
for (Object o : raw) { if (!elementType.isInstance(o)) throw new IllegalArgumentException(...); } - 调用方需明确传递类型信息:
process(new Object[]{s1, s2}, String.class) - 相比
@SuppressWarnings("unchecked"),这种方式把风险暴露在运行时可控节点,便于测试和定位问题
公共 API 中彻底禁用泛型数组参数
工具类、SDK、框架接口一旦暴露 T[] 或 T...,等于把类型安全责任完全推给调用方。而调用方往往无法控制内部数组是否被缓存、转发或修改。
- 对外方法统一接收
List<T>或Iterable<T>,语义清晰且不可变性更易保障 - 若历史 API 已存在,可通过新版本弃用(
@Deprecated)并引导迁移至泛型集合版本 - 内部实现可封装转换逻辑,对外隐藏数组细节


















