坑死我的remove之ConcurrentModificationException

本文深入探讨了在使用ArrayList的forEach方法时遇到ConcurrentModificationException的原因。详细解析了modCount字段的作用及其如何触发异常,强调了正确使用迭代器remove方法的重要性。

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

坑死我的remove之ConcurrentModificationException

先上段代码
    List<String> list = new ArrayList<>();
    Collections.addAll(list,"str1","str2","str3","str4","str5");
    list.forEach(str->{
        if("str1".equals(str))
        list.remove(str);
    });

本来想删除"str1",结果喜提ConcurrentModificationException
接下来就看看,我喜提ConcurrentModificationException的道路上,ArrayList作出了哪些努力与贡献
首先:

 public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);//判空
        final int expectedModCount = modCount;//预期计算=计数,modCount敲黑板,要考
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        //遍历的时候,判断了 modCount==expectedModCount
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();//举报,就是它。
        }
    }

这里我们已经看到ConcurrentModificationException了,但是,我是怎么走到这一步的呢?
上面if里面条件:当modCount != expectedModCount时,抛出异常,也就是说modCount发生了改变?!!!
那他是啥时候改变的,为啥改变了/fd,再往下走走看。我们顺利的拿到了第一个数据,并且返回了,那么
一定是remove搞得鬼咯。看看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);//o!=null,执行fastRemove
                    return true;
                }
        }
        return false;
    }

纵观上面的代码,跟modCount没有关系!那真凶肯定是藏在fastRemove里了

 private void fastRemove(int index) {
        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++啊啊啊,为什么要++,看看modCount作何解释

   /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */

@_@The number of times this list has been structurally modified(以下来自谷歌翻译:此列表已被结构修改的次数)
成功破案,真相只有一个:当list发生结构的更改时,modCount都+1,以防止正在进行中的迭代产生不正确的结果。

正确的remove方法, 应该使用迭代器里面的 remove 方法

 Iterator<Integer> iterator = integers.iterator();
        while (iterator.hasNext()){
            Integer next = iterator.next();
            if (Integer.valueOf(1).equals(next)) {
                iterator.remove();
            }
            if (Integer.valueOf(2).equals(next)) {
                iterator.remove();
            }
        }
### 如何避免List删除元素时的常见问题及陷阱 在Java中,使用`List`删除元素时可能会遇到多种问题,尤其是当需要在遍历过程中同时删除元素时。以下是可能遇到的问题及其解决方案: #### 1. 索引偏移问题 在使用`for`循环遍历`List`并删除元素时,如果按照正序遍历(从索引0开始),删除操作会导致后续元素向左移动,从而引发索引偏移问题[^1]。这可能导致部分元素被跳过或程序逻辑错误。 **解决方案**: - 使用倒序遍历。由于删除操作只会影响后续元素的索引,而不会影响当前及之前的元素,因此倒序遍历可以有效避免索引偏移问题[^2]。 - 示例代码如下: ```java for (int i = list.size() - 1; i >= 0; i--) { if ("b".equals(list.get(i))) { list.remove(i); } } ``` #### 2. `ConcurrentModificationException` 异常 直接在`for`循环或增强型`for`循环中修改集合内容(如删除元素)会导致`ConcurrentModificationException`异常。这是因为默认的迭代器不支持在遍历过程中对集合进行结构性修改[^3]。 **解决方案**: - 使用`Iterator`的`remove`方法。`Iterator`提供的`remove`方法可以在遍历过程中安全地删除元素,而不会抛出`ConcurrentModificationException`异常[^1]。 - 示例代码如下: ```java Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if ("b".equals(element)) { iterator.remove(); } } ``` #### 3. Stream 流过滤 对于Java 8及以上版本,可以使用`Stream`流的`filter`方法来筛选需要保留的元素,并生成一个新的`List`。这种方法避免了直接修改原集合的风险,适合于不可变集合场景[^1]。 **解决方案**: - 示例代码如下: ```java list = list.stream() .filter(element -> !"b".equals(element)) .collect(Collectors.toList()); ``` #### 4. 不推荐的删除方式 直接在增强型`for`循环中调用`List`的`remove`方法是不推荐的做法,因为这会触发`ConcurrentModificationException`异常[^4]。此外,使用`List`的`remove(Object)`方法时需要注意,它删除的是第一个匹配的对象,而不是指定索引位置的元素[^1]。 --- ### 总结 为了避免`List`删除元素时的常见问题和陷阱,建议采用以下方法: - 倒序遍历以避免索引偏移问题[^2]。 - 使用`Iterator`的`remove`方法以避免`ConcurrentModificationException`异常。 - 使用`Stream`流的`filter`方法以实现更简洁、安全的过滤逻辑。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值