为什么ArrayList在使用迭代器迭代元素时不能使用List.remove()删元素,而是使用Iterator.remove()删元素

其实,我相信有一定经验的都已经知道了,把标题的“为什么”去掉读一遍,但是具体是为什么?今天本人跟进源码(jdk7)探了个究竟。注:这篇文章只介绍使用list.remove()抛出ConcurrentModificationException的原因,其它参数及异常不作介绍。

直入主题,首先我们来看下面这段出问题的代码


下面是运行结果


使用iterator.remove()是没有问题的,此处就不粘图了

由运行结果可知,抛异常的地方出现在iterator.next(),而且已经遍历完了“bvBody”,当尝试获取“cvBody”时抛了异常。根据异常第一行可知问题出在ArrayList的内部类Itr(也就是代码中的iterator)的checkForComodification()方法,下面是源码


简单明了,当modCount != expectdModCount时,抛出并发修改异常(记住这个点)。那么,modCount和expectedModCount分别是什么?modCount在ArrayList中的定义如下,简单理解就是记录改变了ArrayList的size(增删操作)的次数

使用迭代器遍历集合,直接使用集合的 `remove()` 或 `add()` 方法会导致并发修改异常(`ConcurrentModificationException`)[^1]。这是因为 Java 集合框架在设计采用了“快速失败”(fail-fast)机制,用于检测集合在迭代过程中是否被外部修改,以保证迭代过程的稳定性和一致性。 ### 原因分析 Java 中的 `Iterator` 接口提供了一种安全地遍历集合的方式,其底层实现中维护了一个“修改次数”计数器(`modCount`),用于记录集合被修改的次数。当调用 `iterator.next()` 方法获取下一个元素,会检查当前集合的 `modCount` 是否与迭代器创建保存的 `modCount` 一致。如果一致,说明集合已经被外部修改过,此会抛出 `ConcurrentModificationException` 异常。 #### 集合的 `remove()` 方法导致异常的原因 当使用集合的 `remove()` 方法删除元素,集合本身的结构会发生变化,而迭代器知道这种变化,因此其内部状态与集合的实际状态一致,从而触发异常。例如: ```java List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (item.equals("b")) { list.remove(item); // 抛出 ConcurrentModificationException } } ``` 上述代码中,调用 `list.remove(item)` 会改变集合的内部结构,导致迭代器的 `modCount` 与集合的 `modCount` 一致,从而抛出异常。 #### 正确删除方式:使用 `iterator.remove()` 为了安全地在遍历过程中删除元素,应使用迭代器提供的 `remove()` 方法。该方法会同步更新集合和迭代器的状态,避免触发并发修改异常。例如: ```java List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (item.equals("b")) { iterator.remove(); // 安全删除元素 } } ``` 使用 `iterator.remove()` 方法可以确保迭代器和集合的内部状态保持一致,从而避免并发修改异常的发生[^1]。 ### 注意事项 - **只能删除当前元素**:`iterator.remove()` 只能删除通过 `next()` 方法返回的最后一个元素不能多次调用 `remove()` 而调用 `next()`。 - **无法添加元素**:Java 的 `Iterator` 接口没有提供 `add()` 方法,因此在遍历无法通过迭代器添加元素。如果需要在遍历过程中添加元素,可以考虑使用 `ListIterator` 接口,它支持双向遍历和添加操作。 - **其他集合类型**:仅 `ArrayList`,`HashSet`、`HashMap` 等集合类型在遍历同样不能使用集合的 `remove()` 方法,否则也会抛出 `ConcurrentModificationException` 异常[^2]。 ### 替代方案 如果需要在遍历过程中对集合进行更复杂的修改(如添加元素),可以考虑以下方法: 1. **在遍历结束后进行修改**:将需要删除或添加的元素收集到另一个集合中,在遍历结束后统一处理。 2. **使用 `ListIterator`**:`ListIterator` 支持在遍历添加元素和双向遍历,适用于 `List` 类型的集合。 3. **使用并发集合**:在多线程环境下,可以使用 `CopyOnWriteArrayList` 等线程安全的集合类型,它们允许在遍历修改集合。 ### 示例代码:使用 `ListIterator` 添加元素 ```java List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); ListIterator<String> listIterator = list.listIterator(); while (listIterator.hasNext()) { String item = listIterator.next(); if (item.equals("b")) { listIterator.add("d"); // 在 "b" 后面插入 "d" } } ``` ### 总结 在使用迭代器遍历集合不能直接使用集合的 `remove()` 或 `add()` 方法,因为这会破坏迭代器的内部状态,导致并发修改异常。应使用迭代器提供的 `remove()` 方法进行安全删除,或者在遍历结束后统一处理集合的修改操作。
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值