Java 中list(ArrayList)的foreach遍历方式调用list的remove删除元素,不抛出ConcurrentModificationException

关于list删除元素的当时有很多,具体可以参考下面这个博客。

https://blog.csdn.net/claram/article/details/53410175

里面提到了list的foreach遍历方式删除元素,会抛出ConcurrentModificationException。foreach是迭代器遍历的一种简写。
但是,如果list中只有两个元素,删除第一个元素时,则不会抛出ConcurrentModificationException异常。

首先看一下迭代器的hasNext()next()方法源码。

        public boolean hasNext() {
        // 当前元素的位置不等于list的size代表还有元素没有遍历
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
        //检查是否有迭代器之外的modify
            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() {
        //expectedModCount 是迭代器自己维护的变量
        //modCount 是list中元素修改的数量
        //如果调用迭代器的方法对list操作,二者是相等的。例如下面的remove方法
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                //保证expectedModCount和modCount相等
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

在看一下ArrayList的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;
    }
    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
    }

假设ArrayList只有两个元素,删除第一个元素的代码如下:

    public static void main(String []args) {
        List<String> list = new ArrayList<>();
        list.add("ddd");
        list.add("aaa");
        for (String string : list) {
            if (string.equals("aaa")) {
                list.remove(string);
            }
            System.out.println(string);
        }
    }

上面代码并没有抛出任何异常,关键在于迭代器遍历第一个元素后,此时cursor=1,modCount=expectedModCount=2,size=2;
删除第一个元素后,cursor=1,modCount=3,expectedModCount=2,size=1;
继续执行迭代器的hasNext()时,发现cursor==size==1,迭代器认为已经遍历结束。并没有继续执行next函数,还没有来得及执行checkForComodification()循环已经结束。

总结
在这种特殊情况下,虽然此时并没有抛出异常,但是也没有按照我们的预想,将两个元素全部遍历完,只遍历了一个元素就结束了,在删除元素时,还应当使用迭代器更加安全。

<think>好的,用户想了解如何在Java遍历某个List。首先,我需要回忆Java中常见的遍历方法,并确保涵盖同的实现方式JavaList接口有多种实现,比如ArrayList和LinkedList,但遍历方法基本一致。常见的遍历方式有几种:传统的for循环、增强型for循环、迭代器Iterator,以及Java 8引入的Stream API。 首先想到的是传统的for循环,使用索引来遍历每个元素。这种方法适用于需要知道元素索引的情况,比如在遍历时需要修改元素或根据位置处理。需要注意的是,这种方法对于LinkedList可能效率高,因为get方法的时间复杂度是O(n)。 然后是增强型for循环(for-each循环),语法更简洁,适用于需要索引的情况。这里要提醒用户,如果在遍历过程中需要删除元素,可能会引发ConcurrentModificationException,这时候应该用迭代器。 接下来是使用Iterator迭代器。Iterator提供了hasNext()和next()方法,可以安全地在遍历删除元素,这是它的优势。同时,ListIterator还支持双向遍历和修改操作,可能在某些场景下有用。 最后是Java 8的Stream API,这种方式适合进行函数式编程,可以结合lambda表达式,使代码更简洁。特别是当需要进行过滤、映射等操作时,Stream非常有用。过,对于简单的遍历,可能有点杀鸡用牛刀的感觉,但用户可能想了解现代Java的特性。 需要确保每种方法的示例代码正确,语法无误。比如在传统for循环中,正确使用size()和get()方法;在迭代器中正确使用while循环;在Stream中正确使用forEach。 还要注意代码格式,使用代码块标记,并正确转义符号。比如尖括号需要使用泛型时,可能需要转义,但在Markdown中应该用反引号包裹代码块,避免格式错误。 用户可能的深层需求是了解遍历方式的优缺点及适用场景,而仅仅是语法。因此,在解释每种方法时,需要简要说明其适用情况,比如需要索引时用传统for循环,需要安全删除元素时用迭代器,简化代码用增强型for循环,复杂操作使用Stream。 另外,要检查是否覆盖了所有常见的方法,有没有遗漏。比如有没有提到forEach方法结合lambda表达式,或者ListIterator的双向遍历过对于基本介绍,可能需要太深入,但至少提到迭代器的remove方法。 最后,确保回答结构清晰,分点列出每种方法,给出示例代码,并说明注意事项,比如并发修改异常的情况。这样用户可以根据自己的需求选择合适的方法。</think>在Java遍历List的常见方法有以下几种,我将逐一介绍并给出示例: ### 1. 传统for循环(带索引) ```java List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); for (int i = 0; i < list.size(); i++) { String element = list.get(i); System.out.println(element); } ``` **特点**: - 需要获取元素索引时使用 - 适用于所有List实现(但LinkedList的`get(i)`性能较差) ### 2. 增强型for循环(for-each) ```java for (String element : list) { System.out.println(element); } ``` **特点**: - 语法最简洁 - 遍历过程能修改集合结构(否则抛出`ConcurrentModificationException`) ### 3. 使用迭代器Iterator ```java Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); System.out.println(element); // 安全删除当前元素 // iterator.remove(); } ``` **特点**: - 唯一支持遍历时安全删除元素方式 - 适用于需要条件删除的场景 ### 4. Java 8+的forEach方法 ```java // Lambda表达式写法 list.forEach(element -> System.out.println(element)); // 方法引用写法 list.forEach(System.out::println); ``` ### 5. 使用ListIterator(双向遍历) ```java ListIterator<String> listIterator = list.listIterator(); // 正向遍历 while (listIterator.hasNext()) { String element = listIterator.next(); System.out.println(element); } // 反向遍历 while (listIterator.hasPrevious()) { String element = listIterator.previous(); System.out.println(element); } ``` ### 选择建议: 1. 需要索引 → 传统for循环 2. 只读遍历 → 增强型for循环 3. 需要修改集合 → 迭代器 4. 函数式编程 → forEach 5. 需要双向操作 → ListIterator **注意事项**: - 遍历过程中直接调用`List.remove()`会抛出异常 - 多线程环境下需要同步处理 - 大数据量时优先选择迭代器方式 根据具体需求选择合适的遍历方式,日常开发中增强型for循环和Java 8的forEach方法使用频率较高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值