以常用集合ArrayList为例,ArrayList
在遍历过程中无法直接修改内部元素的结构(例如通过 remove()
或 add()
方法修改元素),是因为 遍历的过程中修改结构 可能会导致 不一致的行为 、并发修改异常 或 逻辑错误 。
注意:和 ArrayList
一样,Set
集合也不允许在遍历时直接修改其结构(如添加或删除元素),否则会抛出 ConcurrentModificationException
异常。为了安全地修改结构,可以使用 Iterator
的 remove()
方法或者使用removeIf
方法。在需要修改集合结构时,最好先完成遍历操作,避免在遍历过程中进行结构修改,确保程序的稳定性和一致性。
1. ConcurrentModificationException(并发修改异常)
ArrayList
在内部维护了一个修改计数器(modCount
),每次修改 ArrayList
(如删除或添加元素)时,该计数器会增加。在遍历 ArrayList
时,Iterator
会检查该计数器。如果在遍历过程中,集合的结构发生了变化(例如删除或添加元素),Iterator
会发现计数器的值发生变化,因此抛出 ConcurrentModificationException
异常。
这个机制的目的是为了防止多线程环境下发生集合的并发修改,从而导致无法预料的行为。在单线程情况下,虽然没有并发问题,但仍然需要保持结构一致性。
2. 修改结构导致遍历不一致
在遍历过程中修改 ArrayList
结构(比如添加、删除元素)可能会导致遍历过程中的不一致性。例如:
- 删除元素: 如果在遍历过程中删除元素,剩下的元素会向前移动,导致后续的元素错位。可能会导致漏掉一些元素。
- 添加元素: 如果在遍历过程中添加新元素,新的元素会加入到集合的末尾,或者加入到指定位置,这会影响元素顺序,导致遍历时无法保证按预期的顺序访问。
这种行为非常不稳定,并且容易导致程序错误,因此 Java 中的 ArrayList
和其他集合类通常会避免在遍历过程中修改结构。
3. 线程安全性问题
如果 ArrayList
在多个线程中共享使用,且在遍历时对其进行了结构性修改(如添加或删除元素),这可能会导致多个线程间的竞争条件和数据不一致。为了避免此类问题,Java 设计上默认会禁止这种行为,尤其是在多线程环境中。
4. 遍历顺序和数据一致性
遍历 ArrayList
时假定集合的结构是固定的。如果在遍历过程中修改了元素(比如删除或添加元素),会破坏集合的状态,使得遍历结果变得不可预测。为了保证数据一致性,Java 设计者决定不允许在遍历时修改集合的结构。
解决方法
为了在遍历时修改 ArrayList
,可以使用以下几种方式:
-
使用
Iterator
提供的remove()
方法来安全地删除元素,而不是直接通过ArrayList
的remove()
方法:javaIterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if (element.equals("remove")) { iterator.remove(); // 安全地删除元素 } }
-
使用
ListIterator
,它提供了更多操作,例如修改元素、删除元素等:javaListIterator<String> listIterator = list.listIterator(); while (listIterator.hasNext()) { String element = listIterator.next(); if (element.equals("remove")) { listIterator.remove(); // 删除 } else if (element.equals("modify")) { listIterator.set("new value"); // 修改 } }
-
如果要在遍历过程中添加元素,可以使用
ArrayList
的add()
方法,但需要注意添加的元素会被放在遍历结束后,因此可能不在当前遍历中被访问。 -
在移除元素的时候,使用Java 8以后的特性,代码为例
javalist.removeIf(item->{ int size = new LambdaQueryChainWrapper<>(doctorInformationMapper) .eq(DoctorInformation::getKsdm, item.getDepid()) .count(); return size == 0; });