为何iterator,foreach遍历时不能进行remove操做?除了一种状况能够这样(特殊状况)?

Exception in thread "main" java.util.ConcurrentModificationException 并发修改异常引起的思考!java

1 foreach循环删除元素安全

  ①list遍历删除元素时会报错,好比下面删除字符串"aa",也有遍历不报错的例子,看下面的例子并发

public class TestMain {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<String>();
        array.add("cc");
        array.add("aa");
        array.add("bb");
        array.add("aa");   
        for (String str : array) {
            if("aa".equals(str)){
                array.remove(str);
            }
        }

        System.out.println(array.size());

    }
}

console:   java.util.ConcurrentModificationException

  ②下面删除字符串"aa"不会报错spa

public class TestMain {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<String>();
        array.add("cc");
        array.add("aa");
        array.add("bb");
        for (String str : array) {
            if("aa".equals(str)){
                array.remove(str);
            }
        }

        System.out.println(array.size());

    }
}

console : 2
 

提出问题:为何上面都是遍历删除,第二个确没有报错呢?code

结论:其实缘由很简单,由于第二个例子没有走iterator的next方法,删除了字符串"aa"以后,执行hasNext方法返回false直接退出遍历了,hasNext中就是判断cursor != size;此时的cursor是2,而size正好也是2,因此退出了遍历。blog

而第一个例子删除字符串"aa"以后,cursor=2,size=3,因此hasNext方法返回的true,会执行next方法。索引

:只要遍历中remove了,expectedModCount和modCount就不相等了。element

注2cursor 就相似游标,这里表示第几回,size就是元素个数rem

同理:iterator的形式遍历同foreach。字符串

2 上面都是经过foreach的方式进行的,若是用普通for循环会怎么样

public class TestMain {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<String>();
        array.add("cc");
        array.add("aa");
        array.add("bb");
        array.add("aa");
        for(int i = 0;i < array.size();i++){
            if("aa".equals(array.get(i))){
              array.remove(i);
            }
        }
        System.out.println(array.size());

    }
}

console: 2 

  结论: 普通for循环能够正常删除,他是根据索引进行删除的,因此没有影响。

 

根据报错信息能够看到是进入checkForComodification()方法的时候报错了,也就是说modCount != expectedModCount。具体的缘由,是在于foreach方式遍历元素的时候,是生成iterator,而后使用iterator遍历。在生成iterator的时候,

会保存一个expectedModCount参数,这个是生成iterator的时候List中修改元素的次数。若是你在遍历过程当中删除元素,List中modCount就会变化,若是这个modCount和exceptedModCount不一致,就会抛出异常,这个是为了安全的考虑。

看看list的remove源码:

1 private void fastRemove(int index) {
2         modCount++;
3         int numMoved = size - index - 1;
4         if (numMoved > 0)
5             System.arraycopy(elementData, index+1, elementData, index,
6                              numMoved);
7         elementData[--size] = null; // clear to let GC do its work
8     }

remove操做致使modCount++

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;
  public boolean hasNext() {
      return cursor != size;
  }
  public E next() {
            checkForComodification(); try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
        } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
        }
    }


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

 

 对标题的答案: iterator遍历不能remove操做,会报并发修改异常,只有当你remove的元素时倒数第二个元素时不会抛出异常。

以上属于我的心得,不对之处还望大佬指出,若是对你有帮助记得点赞。  
相关文章
相关标签/搜索