要真正兼顾范围检索与强封装,必须先深拷贝再封装子视图,或直接采用不可变实现(如Guava的ImmutableSortedMap),否则仅用Collections.unmodifiableNavigableMap()无法阻断底层修改。

直接用 Collections.unmodifiableNavigableMap() 无法提供“强封装”效果,它只做运行时拦截,不隔离底层数据。要真正兼顾范围检索(如 subMap、headMap)和强封装,必须组合防御性拷贝与视图包装——核心在于切断原始引用、重封子视图、并优选不可变实现替代。
先复制,再封装:避免底层被悄悄修改
不可变视图不是保险箱,只是玻璃罩。如果原始 NavigableMap(比如 TreeMap)还被其他代码持有并修改,你看到的“只读”结果会同步变化。
- 不要直接包装外部传入或共享的 map:例如
Collections.unmodifiableNavigableMap(sharedTreeMap)是危险的 - 务必先做深拷贝(至少是结构拷贝):
new TreeMap(original)或TreeMap.copyOf(original)(Java 12+) - 拷贝后立即丢弃对
original的所有引用,包括局部变量、字段、返回值
子视图也要“二次封印”:subMap/headMap/tailMap 默认仍可变
unmodifiableNavigableMap() 包装后的主视图调用 put() 确实抛 UnsupportedOperationException,但它的 subMap(from, to) 返回的是原始 map 的子视图——未被再次包装,仍是可变的。
- 每次获取子视图后,必须手动再套一层:
Collections.unmodifiableNavigableMap(navigableMap.subMap(k1, k2)) - 若频繁使用子范围,建议封装工具方法:
safeSubMap(map, from, to)内部自动完成拷贝 + 包装 - 注意:
headMap(to, inclusive)和tailMap(from, inclusive)同理,不能跳过二次封装
更推荐的实战路径:用不可变实现替代运行时防护
与其层层补漏,不如从源头杜绝可变性。Java 原生和主流库提供了真正不可变的导航映射:
- Java 12+ 推荐:
ImmutableSortedMap.copyOf(treeMap)(来自 Guava),构建时冻结全部结构,子视图也天然不可变 - Java 14+ 可用:
Map.ofEntries()配合TreeMap构造器,但需自行保证排序逻辑;更稳妥是用TreeMap(Map extends K, ? extends V>)初始化后立即转不可变 - Spring Boot 3+ 配置场景:在
@ConfigurationProperties绑定后,用ImmutableSortedMap.copyOf(loadedMap)替换原字段,并设为final
配置服务封装示例(轻量级、无第三方依赖)
若暂不能引入 Guava,可用以下模式构建安全字典服务:
(Java 17+ 示例)public final class ReadOnlyConfigDict {<br> private final NavigableMap<string object> dict;<br><br> public ReadOnlyConfigDict(Map<string object> source) {<br> // 1. 拷贝并确保有序<br> TreeMap<string object> copy = new TreeMap(source);<br> // 2. 包装主视图<br> this.dict = Collections.unmodifiableNavigableMap(copy);<br> }<br><br> public NavigableMap<string object> range(String from, String to) {<br> // 3. 子视图也封装<br> return Collections.unmodifiableNavigableMap(dict.subMap(from, true, to, false));<br> }</string></string></string></string>
关键点:构造即冻结、子视图不复用、对外只暴露封装后实例。










