1. 编码强制规约

在《阿里巴巴java开发手册》中,针对集合操作,有一项规定,如下:

【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 iterator方式,如果并发操作,需要对 iterator 对象加锁。

public class simpletest {
    public static void main(string[] args) {
        list<string> list = lists.newarraylist();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
 
        //正例
        iterator<string> iterator = list.iterator();
        while (iterator.hasnext()) {
            string item = iterator.next();
            if ("1".equalsignorecase(item)) {
                iterator.remove();
            }
        }
 
        //反例
        for (string item : list) {
            if ("2".equals(item)) {
                list.remove(item);
            }
        }
    }
}

2. 原因分析

在循环或迭代时,会首先创建一个迭代实例,这个迭代实例的expectedmodcount 赋值为集合的modcount.   

每当迭代器使⽤ hashnext() / next() 遍历下⼀个元素之前,都会检测 modcount 变量与expectedmodcount 值是否相等,相等的话就返回遍历;否则就抛出异常【concurrentmodificationexception】,终⽌遍历

如果在循环中添加或删除元素,是直接调用集合的add,remove方法【导致了modcount增加或减少】,但这些方法不会修改迭代实例中的expectedmodcount,导致在迭代实例中expectedmodcount 与 modcount的值不相等,抛出concurrentmodificationexception异常

但迭代器中的remove,add方法,会在调用集合的remove,add方法后,将expectedmodcount 重新赋值为modcount,所以在迭代器中增加、删除元素是可以正常运行的。

可以参考arraylist中的内部私有类itr、listitr的源码

public iterator<e> iterator() {
        return new itr();
    }
 
/**
     * 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 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();
        }
 
  }
   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;
    }

3. 相关知识介绍

3.1. 什么是快速失败(fail-fast)?

快速失败(fail-fast) 是 java 集合的⼀种错误检测机制。在使⽤迭代器对集合进⾏遍历的时候,在多线程下操作⾮安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出concurrentmodificationexception 异常。 

另外,在单线程下,如果在遍历过程中对集合对象的内容进⾏了修改的话也会触发 fail-fast 机制。

举个例⼦:多线程下,如果线程 1 正在对集合进⾏遍历,此时线程 2 对集合进⾏修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进⾏修改,都会导致线程 1 抛出concurrentmodificationexception 异常。

3.2. 什么是安全失败(fail-safe)呢?

采⽤安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,⽽是先复制原有集合内容,在拷⻉的集合上进⾏遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛concurrentmodificationexception 异常。

到此这篇关于为什么在foreach循环中java集合不能添加或删除元素的文章就介绍到这了,更多相关java集合添加或删除元素内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!