Collections.checkedMap() 不校验已有映射,仅在 put/putAll 等写入时检查新键值类型;必须从空泛型匹配的 Map 开始,全程通过包装器操作,否则存在类型绕过风险。

为什么 Collections.checkedMap() 不能校验已有映射的键值类型
Collections.checkedMap() 只在 写入时 检查新插入的 key 和 value 类型,对已存在的映射项完全不干预。它包装的是底层 Map 实例,所有检查逻辑都落在 put()、putAll()、compute() 等修改方法上;而 get()、entrySet()、keySet() 这类读取操作完全绕过类型检查。
这意味着:如果你用原始 HashMap 先塞入 Integer 键和 String 值,再用 checkedMap() 包装它,这个包装器不会回溯校验——它只管你后续往里塞什么。
如何正确创建并使用 checkedMap 保证全程类型安全
必须从空的、类型匹配的底层 Map 开始,并始终通过 checked 包装器操作:
- 底层
Map实例(如new HashMap<String, Integer>())本身不带类型防护,不能直接调用其put() - 必须用
Collections.checkedMap(map, KeyType.class, ValueType.class)得到包装器引用 - 后续所有写入操作必须走这个包装器,不能混用原始 map 引用
- 如果传入泛型不匹配的
Class对象(比如把String.class当作 value 类型),会在构造时抛NullPointerException
示例:
Map<String, Integer> raw = new HashMap<>();
Map<String, Integer> safe = Collections.checkedMap(raw, String.class, Integer.class);
safe.put("a", 1); // OK
safe.put("b", "bad"); // 抛 ClassCastException:expected Integer, got String
checkedMap 在复合操作中的行为边界
它不拦截或重写 Map 的所有方法,部分操作存在“漏检”风险:
-
putAll(Map<? extends K, ? extends V>):会逐个检查每个 entry 的 key/value 类型 -
compute(K, BiFunction<? super K, ? super V, ? extends V>):只检查返回值是否为V类型,不校验 lambda 参数类型(编译期靠泛型约束) -
replaceAll(BiFunction<? super K, ? super V, ? extends V>):同上,仅校验返回值 -
computeIfAbsent(K, Function<? super K, ? extends V>):只校验函数返回值是否为V - 不拦截
Map.Entry.setValue()—— 如果你从entrySet()拿到 entry 并调用setValue(),类型检查完全失效
替代方案比 checkedMap 更可靠吗
纯运行时包装有本质局限:checkedMap() 是桥接式防护,不是类型系统加固。真正强约束需结合:
- 编译期泛型(最基础,但无法防住 raw type 或反射绕过)
- 不可变视图(
Collections.unmodifiableMap()配合构造时校验) - 自定义封装类(如
TypedMap<K,V>,在put()中做instanceof+getClass()双重判断,并禁用entrySet()的可变访问) - 静态分析工具(如 Error Prone 的
CollectionIncompatibleType检查)
注意:checkedMap() 的异常是 ClassCastException,不是更明确的 IllegalArgumentException,且堆栈指向包装器内部,调试时容易误判源头。

















