Collections.unmodifiableMap详解:真的不可修改吗?
在代码审查和面试中,经常会看到这样的写法:
javaMap<String, String> map = new HashMap<>(); Map<String, String> readOnlyMap = Collections.unmodifiableMap(map);很多人认为:
textunmodifiableMap = 不可修改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发生变化。