ArrayList在foreach循环的时候remove元素报错

本文详细解析了Java中使用foreach循环遍历ArrayList时删除元素导致的ConcurrentModificationException异常,介绍了该异常产生的原因及背后的fast-fail机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

示例代码:

public class ArrayListTest {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("hello");
		list.add("hello1");
		list.add("hello2");
		list.add("hello3");
		list.add("hello4");
		list.add("hello5");
		for(String s : list){
			list.remove("hello2");
		}
	}
}

报错结果:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at com.collection.list.ArrayListTest.main(ArrayListTest.java:18)

再看这个问题之前大家先来了解一下foreach的本质是什么,
参考此博文:https://blog.youkuaiyun.com/wangjun5159/article/details/61415263,foreach只是语法糖,本质还是
for(Iterator i$ = a.iterator(); i$.hasNext(); System.out.print(temp));

然后追踪栈轨迹看看源码:

@SuppressWarnings("unchecked")
public E next() {
    checkForComodification();//问题出在这行代码,然后看这个方法是什么?
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

上面两个方法都是private class Itr implements Iterator 这个迭代器实现类的方法。有上面两个方法可以明显的看出 if (modCount != expectedModCount)这个判断上,是这两个值不相等导致抛出了异常,那么这两个值又是什么呢?

//The number of times this list has been <i>structurally modified</i>.
protected transient int modCount = 0;

private class Itr implements Iterator<E> {
   int cursor;       // index of next element to return
   int lastRet = -1; // index of last element returned; -1 if no such
   int expectedModCount = modCount;
}

modCount变量是ArrayList继承的抽象类AbstractList中一个变量,它表示的是对list的修改次数,而迭代器中的expectedModCount 在初始的时候,将modCount赋给了expectedModCount ,通过上面foreach我们看得到迭代器是在已进入for循环的时候初始的这时候modCount是未进行修改的,但是接下来remove有干了什么呢?

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

如上代码,看到remove的元素如果存在的话会进入下面这个方法:

private void fastRemove(int index) {
    modCount++;//在这里操作了modCount这个变量
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, 
	        index,numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

看到这里我们明白了什么时候改变了modCount导致和expectedModCount 不一致抛出了错误。其实,就是java基于fast-fail机制,当集合内部结构发生预期外变化时及时的抛出错误,方便我们检测程序。

### Java 增强 For 循环报错原因及解决方案 #### 错误原因分析 增强型 `for` 循环(也称为 foreach 循环)是一种简化版的迭代器模式,用于遍历集合或数组中的元素。然而,在某些情况下可能会遇到运行时错误。最常见的问题是 **ConcurrentModificationException** 和 **IndexOutOfBoundsException**。 1. **ConcurrentModificationException**: 当尝试在使用增强型 `for` 循环的过程中修改底层集合结构时会触发此异常[^1]。这是因为增强型 `for` 循环内部依赖于隐式的迭代器来访问集合元素。如果在迭代过程中直接调用了诸如 `add()` 或 `remove()` 方法,则会导致集合的状态发生变化,从而违反了迭代器的预期行为。 2. **IndexOutOfBoundsException**: 这种情况通常发生在操作列表时超出索引范围的情况。例如,在删除某个元素后未正确调整游标位置可能导致后续访问越界的逻辑错误[^3]。 --- #### 解决方案 ##### 针对 ConcurrentModificationException 的解决方法: - 使用显式迭代器并配合其内置的方法完成安全的操作: 显式创建一个 `Iterator` 对象,并利用它的 `remove()` 方法代替直接作用于集合上的移除动作。这样能够确保即使发生了结构性变化也不会破坏当前正在使用的迭代状态。 ```java List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if ("b".equals(element)) { // 条件判断 iterator.remove(); // 安全地移除符合条件的项 } } System.out.println(list); // 输出结果为 [a, c] ``` - 替代数据结构:考虑采用支持并发修改的数据类型如 `CopyOnWriteArrayList`,它会在每次写入操作前复制整个底层数组副本,虽然牺牲了一定性能但提供了线程安全性保障。 ##### 处理 IndexOutOfBoundsException 的策略: - 调整循环条件或者重新设计算法流程以防止因动态更新容器大小而导致意外终止; - 如果确实需要边遍历边删减项目数较多的大规模序列化对象建议先收集待清理的目标再统一执行清除命令而不是逐条即时生效。 ```java List<Integer> numbers = new ArrayList<>(); numbers.add(1); numbers.add(2); numbers.add(3); // 收集要被移除的对象 List<Integer> toRemove = new ArrayList<>(); for(Integer num : numbers){ if(num % 2 != 0){ toRemove.add(num); } } // 统一移除 numbers.removeAll(toRemove); System.out.println(numbers); // 结果应显示仅保留偶数值 [2] ``` --- ### 总结 为了有效地规避上述提到的各种潜在陷阱,开发人员应当熟悉不同场景下的最佳实践以及各自适用的技术手段。无论是选用更合适的工具还是优化现有代码架构都能显著提升软件系统的稳定性和可维护性水平。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值