Collections.unmodifiableMap详解:真的不可修改吗?

Collections.unmodifiableMap详解:真的不可修改吗?

在代码审查和面试中,经常会看到这样的写法:

java 复制代码
Map<String, String> map = new HashMap<>();

Map<String, String> readOnlyMap =
     Collections.unmodifiableMap(map);

很多人认为:

text 复制代码
unmodifiableMap
=
不可修改Map

但实际上:

text 复制代码
它并不是真正的不可变Map

本文将从源码角度深入分析:

  • unmodifiableMap到底是什么?
  • 为什么修改原Map会影响只读Map?
  • 它和ImmutableMap有什么区别?
  • 实际项目中应该如何使用?

目录

  • 什么是unmodifiableMap
  • 基本使用方式
  • 为什么不能直接修改
  • 修改原Map会发生什么
  • 源码分析
  • 设计思想
  • 与ImmutableMap区别
  • 常见使用场景
  • 面试高频问题
  • 最佳实践
  • 总结

一、什么是unmodifiableMap

JDK提供:

java 复制代码
Collections.unmodifiableMap()

用于创建:

text 复制代码
只读Map视图

方法定义:

java 复制代码
public static <K,V> Map<K,V>
unmodifiableMap(Map<? extends K, ? extends V> m)

示例:

java 复制代码
Map<String, String> map =
        new HashMap<>();

map.put("name", "Tom");

Map<String, String> readOnlyMap =
        Collections.unmodifiableMap(map);

此时:

java 复制代码
readOnlyMap

表面上看已经变成:

text 复制代码
不可修改Map

二、基本使用方式

创建Map:

java 复制代码
Map<String, String> map =
        new HashMap<>();

map.put("name", "Tom");

map.put("age", "18");

包装:

java 复制代码
Map<String, String> readOnlyMap =
        Collections.unmodifiableMap(map);

读取:

java 复制代码
System.out.println(
        readOnlyMap.get("name")
);

输出:

text 复制代码
Tom

正常访问。


三、为什么不能直接修改

尝试:

java 复制代码
readOnlyMap.put(
        "city",
        "Beijing"
);

运行:

text 复制代码
Exception in thread "main"

java.lang.UnsupportedOperationException

再试:

java 复制代码
readOnlyMap.remove("name");

结果:

text 复制代码
UnsupportedOperationException

看起来确实:

text 复制代码
无法修改

很多人到这里就认为:

text 复制代码
这是一个真正不可变Map

实际上并不是。


四、修改原Map会发生什么

看下面代码:

java 复制代码
Map<String, String> map =
        new HashMap<>();

map.put("name", "Tom");

Map<String, String> readOnlyMap =
        Collections.unmodifiableMap(map);

此时输出:

java 复制代码
System.out.println(readOnlyMap);

结果:

text 复制代码
{name=Tom}

修改原Map:

java 复制代码
map.put("age", "18");

再次输出:

java 复制代码
System.out.println(readOnlyMap);

结果:

text 复制代码
{name=Tom, age=18}

发现:

text 复制代码
readOnlyMap也变化了

这说明:

text 复制代码
unmodifiableMap

并没有复制数据

五、内存结构分析

创建:

java 复制代码
Map<String, String> map =
        new HashMap<>();

内存:

text 复制代码
map

↓

HashMap对象

执行:

java 复制代码
Collections.unmodifiableMap(map)

后:

text 复制代码
map

↓

HashMap对象

↑

readOnlyMap

图示:

text 复制代码
map
  │
  ▼

HashMap

  ▲
  │

readOnlyMap

两个引用:

text 复制代码
指向同一个HashMap

因此:

修改:

java 复制代码
map.put(...)

实际上修改的是:

text 复制代码
同一个对象

六、源码分析

进入源码:

java 复制代码
Collections.unmodifiableMap(map)

返回:

java 复制代码
return new UnmodifiableMap<>(m);

继续查看:

java 复制代码
private final Map<? extends K,
        ? extends V> m;

构造方法:

java 复制代码
UnmodifiableMap(
        Map<? extends K,
        ? extends V> m) {

    this.m = Objects.requireNonNull(m);
}

核心代码:

java 复制代码
this.m = m;

注意:

这里是:

text 复制代码
引用赋值

不是:

java 复制代码
new HashMap<>(m)

因此:

text 复制代码
底层仍然是原Map

七、为什么put会抛异常

继续看源码:

java 复制代码
public V put(K key, V value) {

    throw new UnsupportedOperationException();
}

remove:

java 复制代码
public V remove(Object key) {

    throw new UnsupportedOperationException();
}

clear:

java 复制代码
public void clear() {

    throw new UnsupportedOperationException();
}

也就是说:

text 复制代码
修改操作全部被禁止

但是:

text 复制代码
读取操作正常放行

例如:

java 复制代码
get()
containsKey()
size()

最终委托给:

java 复制代码
m

执行。


八、设计思想

为什么JDK这样设计?

原因:

text 复制代码
性能

如果:

java 复制代码
new HashMap<>(m)

每次都复制:

text 复制代码
时间复杂度 O(n)

而当前实现:

text 复制代码
只包装一层

复杂度:

text 复制代码
O(1)

创建成本极低。


九、与ImmutableMap区别

很多人容易混淆:

java 复制代码
Collections.unmodifiableMap

和:

java 复制代码
Guava ImmutableMap

unmodifiableMap

特点:

text 复制代码
只读视图

修改原Map:

text 复制代码
会影响结果

示例:

java 复制代码
map.put("age", "18");

readOnlyMap同步变化。


ImmutableMap

例如:

java 复制代码
ImmutableMap<String,String> map =
        ImmutableMap.of(
                "name",
                "Tom"
        );

特点:

text 复制代码
真正不可变

修改原数据:

text 复制代码
不会影响

创建时:

text 复制代码
复制数据

形成独立对象。


十、JDK9之后的新选择

JDK9新增:

java 复制代码
Map.of()

例如:

java 复制代码
Map<String,String> map =
        Map.of(
                "name",
                "Tom",
                "age",
                "18"
        );

尝试:

java 复制代码
map.put("city","BJ");

直接报错:

text 复制代码
UnsupportedOperationException

并且:

text 复制代码
是真正不可变

推荐:

text 复制代码
JDK9+

优先Map.of()

十一、实际开发场景

配置项返回

例如:

java 复制代码
public Map<String,String> getConfig() {

    return Collections.unmodifiableMap(
            configMap
    );
}

防止调用方:

java 复制代码
config.put(...)

修改配置。


缓存数据暴露

例如:

java 复制代码
public Map<Long,User> getCache() {

    return Collections.unmodifiableMap(
            cache
    );
}

保护内部缓存。


SDK设计

很多开源框架:

text 复制代码
Spring

MyBatis

Dubbo

都大量使用:

java 复制代码
Collections.unmodifiableXXX

暴露只读集合。


十二、面试高频问题

面试题1

unmodifiableMap是真正不可变Map吗?

答案:

text 复制代码
不是

面试题2

本质是什么?

答案:

text 复制代码
只读视图(Read Only View)

面试题3

为什么修改原Map会影响结果?

答案:

text 复制代码
底层引用同一个Map

面试题4

put为什么报错?

答案:

java 复制代码
直接抛出

UnsupportedOperationException

面试题5

如何实现真正不可变Map?

答案:

text 复制代码
Guava ImmutableMap

JDK9 Map.of()

面试题6

unmodifiableMap时间复杂度是多少?

创建过程:

text 复制代码
O(1)

因为:

text 复制代码
没有复制数据

十三、最佳实践

推荐:

java 复制代码
return Collections.unmodifiableMap(
        cacheMap
);

用于:

text 复制代码
保护内部数据

如果需要真正不可变:

推荐:

java 复制代码
Map.of(...)

或者:

java 复制代码
ImmutableMap

避免误以为:

java 复制代码
unmodifiableMap

能够阻止:

java 复制代码
原Map修改

实际上:

text 复制代码
做不到

总结

Collections.unmodifiableMap 的本质:

text 复制代码
只读Map视图

而不是:

text 复制代码
真正不可变Map

其核心实现:

java 复制代码
this.m = m;

只是持有原Map引用。

因此:

text 复制代码
修改原Map

会影响只读Map

但:

java 复制代码
put()
remove()
clear()

等修改操作会直接抛出:

text 复制代码
UnsupportedOperationException

牢记一句面试经典回答:

Collections.unmodifiableMap本质上是一个只读包装器(Wrapper),它并不会复制底层数据,而是持有原Map引用,因此只能限制当前视图修改数据,无法阻止原Map发生变化。


相关推荐
江米小枣tonylua1 小时前
关掉 VSCode:在 NeoVim12 上配置 Claude Code
前端·程序员
点燃大海1 小时前
SpringAI构建智能体
java·spring boot·spring·springai智能体
xier_ran1 小时前
【infra之路】02_RadixAttention与KV_Cache管理
java·spring boot·spring
2301_773643621 小时前
ceph镜像
前端·javascript·ceph
黑马师兄1 小时前
RAG混合检索深度解析:让AI真正找到你要的内容
java·人工智能·ai·agent·rag·ai-native
码客日记1 小时前
Spring Boot 配置文件敏感信息加密(Jasypt 企业级完整方案)
java·spring boot·git
程序员黑豆1 小时前
AI全栈开发之Java:什么是JDK
前端·后端·ai编程
To_OC1 小时前
万字解析《JS语言精粹》之第四章:函数15大核心精髓(JS灵魂核心)
前端·javascript·代码规范
mqcode2 小时前
Vue3 + Element Plus + Vite 企业级后台框架搭建全流程
前端