Java 8与Java 9+中HashSet迭代器移除修改后对象的行为差异原因解析
Why does Iterator.remove() fail to modify a HashSet after mutating elements in Java 8, but works in Java 9+?
这是个非常有意思的版本差异问题,核心原因在于Java 8和Java 9+中HashMap(因为HashSet底层完全依赖HashMap实现,元素会作为HashMap的key存储)的迭代器remove()方法实现逻辑截然不同。
先理清场景本质
你的代码中,Date对象的hashCode()是基于getTime()的返回值实现的。当你调用date.setTime()修改时间时,实际上直接改变了该对象的hashCode。而HashSet内部的元素存储位置是基于元素初始的hashCode计算的——这就为后续的删除操作埋下了隐患。
Java 8中删除失败的原因
在Java 8的HashMap实现里,迭代器的remove()方法是这么工作的:
- 当你调用
iterator.remove()时,它会调用HashMap的removeEntryForKey(Object key)方法,这个方法会重新计算当前key(也就是你修改后的Date对象)的hash值。 - 然后用这个新的hash值去定位
HashMap中的桶位置,但此时目标元素实际存储的桶是基于修改前的旧hash值计算的。用新hash值找不到对应的entry,自然无法完成删除操作,所以集合大小始终保持为2。
这里要注意:迭代器能正常遍历到两个元素,是因为迭代是基于HashMap原有的桶结构进行的,和元素当前的hashCode无关——但删除操作依赖的是当前元素的新hash值去查找,所以失败了。
Java 9+中删除成功的改进
从Java 9开始,JDK团队优化了HashMap迭代器的实现:
- 迭代器在遍历过程中会直接持有当前遍历到的
Node节点(也就是存储元素的容器对象)的引用。 - 当调用
remove()时,不再需要重新计算key的hash值去查找entry,而是直接操作当前持有的Node对象,通过它在链表或红黑树中的直接位置完成移除。 - 不管元素的
hashCode有没有被修改,迭代器都能精准定位到要删除的节点,所以删除操作顺利完成,集合大小变为0。
验证你的输出
Java 8的输出里,虽然迭代器能打印出两个修改后的元素,但Size after remove iteration: 2证明删除操作根本没生效;而Java 9+的Size after remove iteration: 0则说明优化后的逻辑解决了这个问题。
内容的提问来源于stack exchange,提问作者Viktor Taranenko
相关产品推荐
相关产品推荐

