Java使用foreach遍历集和时不能add/remove的原因剖析

foreach 与 Iterator

我们知道,在Java中使用foreach对集和进行遍历时,是无法对该集和进行插入、删除等操作,比如以下代码:

    for(Person p : personList){
        if(StringUtil.isBlank(p.getName())){
            personList.remove(p);
        }
    }

执行代码,报以下异常:

    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
        at java.util.ArrayList$Itr.next(ArrayList.java:859)
        at com.xiuhao.service.ForeachDemo.main(ForeachDemo.java:20)

根据错误提示,定位ArrayList的源码,找到以下内容:

    /**
     * An optimized version of AbstractList.Itr
     */
    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;
        Itr() {}
        public boolean hasNext() {
            return cursor != size;
        }
        @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];
        }
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        ...

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

即foreach的实现过程中使用Iterator的next()方法来实现遍历。在每次调用该方法前,首先执行checkForComodification()方法检查modCountexpectedModCount的值是否相等,如果不相等则直接抛出上文中的 ConcurrentModificationException

再来查看modCountexpectedModCount的值是如何定义的,在代码的开头部分初始化expectedModCount = modCount,即两者的值是相等的。modCountArrayList父类AbstractArrayList的成员变量,其定义如下:

    /**
     * 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.
     */
    protected transient int modCount = 0;

由此可见,modCount纪录了有改变list大小等结构性变化或者其他使得遍历过程中产生不正确的结果的其它方式的次数,它的初始值为0,当每次迭代器被调用时,其值会被初始化成该list的大小。

当执行到personList.remove(p);时,查看remove()方法的源码:

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        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

        return oldValue;
    }

发现该方法执行modCount++;,改变了值大小,当迭代器再次执行next()方法并调用checkForComodification()时,由于expectedModCount的值没有改变,因此会抛出 ConcurrentModificationException异常。同理,list的add方法同样会出发modCount++;,因此,无法使用foreach循环对list进行添加删除等操作。

那么,如何通过遍历进行list的增删操作呢,再次回到Iterator的源代码:

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount; //重新设置expectedModCount
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

注意到Iteratorremove()方法重新设置了expectedModCount = modCount;,因此当再次执行next()时保证了两个参数一直相同,不会抛出异常,代码如下:

    Iterator<Person> iterator =  personList.iterator();
    while (iterator.hasNext()) {
        if(StringUtil.isBlank(iterator.next().getName())){
            iterator.remove();
        }
    }

此外,对集和进行遍历编辑的方法包括:

  • 直接使用普通for循环进行操作
    int size = personList.size();
    for(int i=0; i<size ;i++){
        if(StringUtil.isBlank(personList.get(i).getName())){
            personList.remove(i);
        }
    }
  • 使用Java 8中提供的filter过滤
List<Person> persons = personList.stream().filter(persron -> StringUtil.isNotBlank(persron.getName())).collect(Collectors.toList());
  • 使用fail-safe的集合类,如ConcurrentHashMap

原文地址:https://www.cnblogs.com/liesun/p/11663199.html

时间: 2024-10-05 23:25:28

Java使用foreach遍历集和时不能add/remove的原因剖析的相关文章

为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作

这个问题我在实际写代码中也遇到过,当时还很疑惑,刚看到这里有一些解释得挺清楚,记录一下: 原文地址为:https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650123395&idx=1&sn=b089eac4f561f58ee8a92602db17f577&chksm=f36bb1a2c41c38b4f36c1a84f205e188a6a8aa2684f316e4dcb8d1162b6e94b970c670b2e5b

php 中遍历数组时使用引用出现的问题

今天在使用foreach遍历数组时发现,当使用&时会出现问题: $arr = array( array('id' => 100, 'error'=> 'aa'), array('id' => 101, 'error'=> 'bb'), ); foreach($arr as &$value) { if($value['id'] == 101) $value['error'] = 'test'; } var_dump($arr); foreach($arr as $va

foreach遍历专题

foreach 我们在使用foreach遍历数组时,往往忘了无从下手,介绍一下常用的foreach操作.当然,我们要知道foreach的原理: 举一反三 如果我们又想得到将一个二维数组转化成一个表格,又该怎么办呢? <?php header("Content-type:text/html;Charset=utf-8"); $arr=array          (                    'stu0'=>array                       

list在遍历过程中的add/remove

平时开发过程中,很多人估计都遇到过一个问题:在遍历集合的的过程中,进行add或者remove操作的时候,会出现2类错误,包括:java.util.ConcurrentModificationException for in遍历过程中add/remove导致的错误java.lang.IndexOutOfBoundsException 越界错误,for循环的时候删除元素. 那么我们应该怎样避免这个问题呢? 首先对于add操作:建议利用原生的for循环.remove操作利用foreach操作 具体代码

为什么阿里巴巴Java开发手册中强制要求不要在foreach循环里进行元素的remove和add操作?

在阅读<阿里巴巴Java开发手册>时,发现有一条关于在 foreach 循环里进行元素的 remove/add 操作的规约,具体内容如下: 错误演示 我们首先在 IDEA 中编写一个在 foreach 循环里进行 remove 操作的代码: import java.util.ArrayList; import java.util.List; public class ForEachTest { public static void main(String[] args) { List<S

java容器中遍历循环Iterator 和 Foreach 循环

最近遇到这样一个问题,要删除一个集合中的某个元素,该怎么解决? 我自己写了两段代码来删除,都报了一个错java.util.ConcurrentModificationException:为了让更多可能没注意到这个点的程序猿注意一下,我在这里分享下~ 先看代码 1 package com.zwt1234; 2 3 import java.util.HashSet; 4 import java.util.Iterator; 5 import java.util.Set; 6 7 public cla

Java中forEach, 用来遍历数组

这里的for是Java中forEach, 用来遍历数组的.for(int i : d) 就是遍历int型数组d的 每一次访问数组d的时候读取的数据放入int型的i中.和for(int i=0;i<d.length();i++)是一样的,但是forEach的可用场合较多. public class e1 {public static void main(String[] args){ int[]d=new int[] {1,2,3,4,64,1234,3124,657,22}; System.ou

java foreach遍历的前提条件

自我总结,欢迎拍砖,不胜感激! 目的: 加深foreach遍历的影响 证明:foreach遍历的前提条件是:list !=null ,而不是:list !=null && list.size() >0 说明: jdk -version : 1.6 public static void main(String[] args) { List<String> list = null; // List<String> list = new ArrayList<S

用&lt;forEach&gt;遍历list集合时,提示我找不到对象的属性

<c:forEach items="${list}" var="item"> <tr> <td>${item.UserId}</td> <td>${item.UserName}</td> </tr> </c:forEach> 用<forEach>遍历list集合时,提示我找不到对象的属性.因为他封装的时候 他会主动将第一位改成大写 如果你的是 name 封装好