Collections.checkedList() 返回新包装器而不修改原列表,需确保所有写操作仅通过该包装引用进行,否则类型安全失效;它仅对add、set等写方法做instanceof检查,读操作无开销,但无法防止反射或原始引用误用。

为什么 Collections.checkedList() 不能直接“加固”已有列表
它不修改原列表,而是返回一个新包装器对象——原列表依然裸奔,任何绕过包装器的写入(比如直接调用原列表的 add())都不会被检查。你必须确保所有写操作都走这个包装后的引用,否则类型安全形同虚设。
- 原列表(如
new ArrayList<String>())本身无泛型运行时约束,JVM 允许rawList.add(new Integer(42)) -
Collections.checkedList(rawList, String.class)返回的是CheckedList实例,它在add()、set()、addAll()等方法里插入前做instanceof检查 - 若同时持有原始引用和包装引用,且混用,就等于留了后门
正确初始化和使用 checkedList 的典型写法
最安全的方式是:从创建起就只暴露包装后的引用,原列表私有化或丢弃。
List<String> safeList = Collections.checkedList(
new ArrayList<>(),
String.class
);
- 不要写
ArrayList<String> raw = new ArrayList<>(); List<String> safe = Collections.checkedList(raw, String.class);—— 这样raw仍可被误用 - 传入的
Class参数必须与目标泛型类型一致;用String.class包装ArrayList<Integer>不报错,但后续add("hello")会抛ClassCastException - 该包装器对
get()、size()等读操作无额外开销,写操作仅多一次类型判断,性能影响极小
checkedList 对哪些操作做检查?哪些不检查?
它只拦截明确可能引入非法对象的写方法,不覆盖所有行为。
- ✅ 检查:
add()、set()、addAll()、retainAll()、removeAll()(注意:后者检查的是要移除的元素是否匹配类型?不检查——它只确保不会因操作意外注入非法类型) - ❌ 不检查:
get()、iterator()、listIterator()返回的迭代器——但这些迭代器的set()和add()方法会被检查 - ⚠️ 特别注意:
toArray()不检查,但返回的数组类型由运行时实际内容决定;若原列表已被污染(比如有人绕过包装器加了Integer),toArray(String[].class)会抛ArrayStoreException
和泛型擦除、ClassCastException 的关系
它不是编译期类型系统的一部分,而是在运行时补一层防御——所以它无法防止所有类型错误,只防“写入时类型不符”。
- 编译器允许
safeList.add((Object)42)(因为泛型已擦除),但运行时checkedList会立刻抛ClassCastException - 它不能防止反射绕过(如通过
Field.setAccessible(true)直接改底层数组)、也不能防止序列化/反序列化污染 - 如果底层列表本身是不可变的(如
Collections.emptyList()),checkedList()会包装成功,但所有写操作都会立即失败——这有时是故意为之,用于构造带类型校验的只读契约

















