collections.unmodifiablenavigablemap() 并非真正完全只读,因不阻止底层 map 被修改,且其 submap() 等返回的子视图仍可变;需防御性拷贝(如 new treemap(original))或改用 immutablesortedmap.copyof() 才能实现安全不可变。

为什么 Collections.unmodifiableNavigableMap() 不能真正“完全只读”
它只是给原 NavigableMap 套了一层不可变外壳,但**不阻止底层 map 被修改**。如果原始 map(比如 TreeMap)后续被其他引用改写,你通过 unmodifiable 视图看到的数据也会变——这不是你想要的“完全只读”。
常见错误现象:UnsupportedOperationException 确实不会在调用 put()、remove() 时抛出,但 subMap()、headMap()、tailMap() 返回的子视图仍是可变的(它们默认继承原 map 的可变性),而且这些子视图没被再次包装。
- 必须确保原始 map 不再被任何其他代码持有或修改(最好在包装前就断开所有强引用)
- 对
subMap()等返回值要手动再套一层Collections.unmodifiableNavigableMap() - 注意:Java 8+ 中
unmodifiableNavigableMap()返回的是UnmodifiableNavigableMap,它重写了全部导航方法,但子视图仍需额外防护
如何安全地构造一个真正隔离的只读 NavigableMap
最稳妥的做法是用不可变实现替代封装,而非依赖运行时防护。推荐使用 ImmutableSortedMap(来自 Guava)或 Java 10+ 的 Map.copyOf() + 自定义只读包装器。
- Guava 方案(推荐):
ImmutableSortedMap.copyOf(originalMap)—— 它深拷贝键值对,并返回真正不可变、线程安全、且保持排序的实例 - 纯 JDK(Java 10+):
Map.copyOf(new TreeMap(originalMap)),但注意Map.copyOf()返回的是普通Map,丢失了NavigableMap接口能力;如需导航方法,得自己包装或升级到 Guava - 若必须用标准库且坚持用
unmodifiableNavigableMap(),请先做防御性拷贝:Collections.unmodifiableNavigableMap(new TreeMap(originalMap))
unmodifiableNavigableMap() 的子视图陷阱与修复
调用 unmodifiableNavigableMap(map).subMap(from, to) 返回的仍是可变的 TreeMap 子视图(除非原 map 是 ConcurrentSkipListMap,但它的子视图也非不可变)。这是最容易被忽略的漏洞。
- 错误示例:
unmodMap.subMap("a", "z").put("x", 1)可能成功,且影响原始数据 - 正确做法:对每个子视图显式再包装,例如
Collections.unmodifiableNavigableMap(unmodMap.subMap(from, to)) - 更安全的替代:用
ImmutableSortedMap的subMap()方法,它直接返回新的不可变子集,不共享状态
性能和兼容性注意事项
每次调用 subMap()、headMap() 都会创建新视图对象;反复包装会增加 GC 压力。而 ImmutableSortedMap 的子视图是结构共享的,更轻量。
-
Collections.unmodifiableNavigableMap()在 Java 6 引入,兼容性好,但语义脆弱 -
ImmutableSortedMap要求 Guava ≥ 12.0,且 key 必须实现Comparable或传入Comparator - 如果原 map 很大,
new TreeMap(original)拷贝有 O(n log n) 开销,而 Guava 的copyOf()是 O(n),且内部做了优化
真正“完全只读”的关键不在包装函数本身,而在是否切断了所有可变入口——包括原始引用、子视图暴露、以及并发修改的可能性。别让 unmodifiable 给你一种虚假的安全感。










