map.entryset()返回的entry是复用的可变视图对象,非每次新建;遍历时缓存entry引用或修改map易导致数据错乱或concurrentmodificationexception。

Java 中 Map.entrySet() 返回的 Set 是一个“视图”(view),其内部 Entry 对象的生命周期和复用行为,是许多开发者踩坑的根源——尤其在遍历中修改 Map、或缓存 Entry 引用时。
Entry 是视图对象,不独立存在
Map.Entry 不是每次调用 iterator().next() 都新建的实例,而是由具体 Map 实现(如 HashMap、TreeMap)复用的**可变容器对象**。以 HashMap 为例:
- 其内部
EntrySet的迭代器返回的Entry,实际是哈希桶中同一个Node实例的引用; - 多次调用
next()时,该Entry对象的key、value字段会被原地更新,指向下一个键值对; - 这意味着:两次
next()返回的Entry可能是同一个对象,只是内容被覆盖了。
常见陷阱:缓存 Entry 引用导致数据错乱
以下代码看似安全,实则危险:
Map<string integer> map = new HashMap();
map.put("a", 1);
map.put("b", 2);
List<map.entry integer>> entries = new ArrayList();
for (Map.Entry<string integer> e : map.entrySet()) {
entries.add(e); // ❌ 错误:添加的是同一对象的多个引用
}
// 此时 entries.get(0) 和 entries.get(1) 可能指向同一个 Entry 实例!
</string></map.entry></string>
执行后 entries 中两个元素的 key/value 很可能都变成最后一对("b", 2),因为底层 Entry 被复用了。
正确做法是立即提取字段,或创建新对象:
- 只保存
e.getKey()和e.getValue()(不可变类型更安全); - 需要保留 Entry 结构时,显式构造新实例:
new AbstractMap.SimpleImmutableEntry(e); - 避免在循环外长期持有
entrySet()中的Entry引用。
遍历时修改 Map 可能引发 ConcurrentModificationException 或状态不一致
entrySet() 视图与原 Map 共享结构。若在迭代过程中调用 map.put()、map.remove()(非 Iterator.remove()),多数实现会触发 ConcurrentModificationException。
但更隐蔽的问题是:某些 Map(如 ConcurrentHashMap)允许弱一致性迭代,其 Entry 可能反映“某个时间点”的快照,且 Entry 本身仍被复用 —— 迭代中并发修改可能导致某次 next() 返回的 Entry 内容与预期不符(例如 key 为 null、value 已过期)。
安全实践:
- 遍历时仅通过
Iterator.remove()删除; - 如需边遍历边增删,先收集操作意图(如待删 key 列表),遍历结束后统一处理;
- 高并发场景下,优先使用
ConcurrentHashMap的专用方法(如forEach()、reduceEntries()),避免手动持住 Entry。
不同 Map 实现的差异需留意
虽然 JDK 规范未强制要求 Entry 复用,但主流实现均采用此优化:
-
HashMap、LinkedHashMap:Entry 复用明确,迭代器返回的Entry是内部Node的包装,字段可变; -
TreeMap:基于红黑树,其Entry同样被复用,且setValue()会直接修改树节点; -
ConcurrentHashMap(JDK 8+):迭代器弱一致性,Entry 是快照,但仍是复用对象,且setValue()不影响原 Map; -
Collections.unmodifiableMap()包装后的 entrySet:底层仍依赖原 Map 行为,复用逻辑不变。
因此,不能依赖“Entry 是 immutable”或“每次 new”,应始终按“可变视图对象”来对待。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











