collections.checkedlist 仅在运行时写入操作(add/set/addall)时检查类型,声明、读取、转型、toarray、stream 等均不检查;不校验初始列表内容,也不拦截 get 后强转失败。

为什么 Collections.checkedList 看起来没起作用?
它确实能捕获泛型违规,但只在「运行时写入」时检查——声明时、读取时、转型时都不报错。很多人误以为加了 checkedList 就像编译器一样全程盯防,结果往里塞错类型后,直到调用 add、set 或 addAll 才抛 ClassCastException。
常见错误现象:List<string> list = Collections.checkedList(new ArrayList(), String.class); list.add(123); // 运行时报错:ClassCastException: Integer cannot be cast to String</string>
- 它不检查构造后的原始列表内容(比如你传入一个已混入
Integer的ArrayList) - 不拦截
get()返回值的后续使用(返回Object后强转失败是你的事) - 对
toArray()、stream()等间接操作无防护
Collections.checkedList 和 ArrayList 直接泛型的区别
直接声明 List<string> list = new ArrayList();</string> 是纯编译期约束,擦除后完全没运行时保护;而 checkedList 是运行时守门员,靠包装代理实现检查。
- 性能开销:每次写操作多一次
instanceof判断,高频写场景可感知(如循环 add 万级元素) - 兼容性:返回的是
CheckedList(私有静态类),不是ArrayList,不能强转或依赖其特有方法 - 泛型擦除后行为一致:两者在反射或序列化时都丢失类型信息,
checkedList不改变这点
哪些操作会触发类型检查?
只对明确修改元素内容的方法生效,且检查目标是「要写入的值」是否匹配泛型类型参数。
- 触发检查:
add(E)、set(int, E)、addAll(Collection extends E>)、add(int, E) - 不触发检查:
get(int)、size()、isEmpty()、contains(Object)(注意:这里Object是任意类型,不会校验)、iterator().next() - 特殊:如果用
list.set(0, null),只要泛型类型允许null(即非基本类型包装类),就不会报错
容易被忽略的坑:包装的是视图,不是数据源头
checkedList 返回的是原列表的带检查包装器,不是拷贝。如果你保留了对底层列表的引用,绕过包装器直接操作,所有检查就失效了。
示例:
List raw = new ArrayList(); List<String> checked = Collections.checkedList(raw, String.class); raw.add(42); // 完全不报错!checked.get(0) 取出来再强转才崩
- 永远不要暴露原始列表引用
- 避免和
Arrays.asList()混用(后者返回不可扩容列表,checkedList包装后add仍会抛UnsupportedOperationException) - 注意线程安全:它不提供同步,多线程写需额外加锁
真正难防的不是写错类型,而是有人绕过包装器、或在检查边界外做类型转换——这时候泛型安全就只剩一层纸。









