ArrayList系列--ArrayList集合为什么不能使用foreach增删改?

ArrayList集合为什么不能使用foreach增删改?

前言:foreach语法糖

ArrayList中使用foreach语法糖,实际内部采用的iterator迭代器方式

List<String> list = new ArrayList<String>(10);
list.add("1");
list.add("2");
//方式1
for (String str : list) {
   
    ....
}

//方式二
for (Iterator i=list.iterator(); i.hasNext(); ) {
    String str = i.next();
}
    
//实现了Iterable接口的类采用iterator生成迭代器 所有Collection集合类都实现了该接口
/**Returns an iterator over the elements in this list in proper sequence.
 *The returned iterator is fail-fast.
 **/
 public Iterator<E> iterator() {
        return new Itr();
    }

具体foreach语法糖介绍:参考朱小厮的博客https://blog.csdn.net/u013256816/article/details/50736498

其中数组和集合类有差异

正文:foreach中增删改报错

请看下面代码片段

List<String> arrayList1 = new ArrayList<String>();
arrayList1.add("1");
arrayList1.add("2");
for (String s : arrayList1) {
 if("1".equals(s)){
  //不报错
 arrayList1.remove(s);
 }
}
List<String> arrayList2 = new ArrayList<String>();
arrayList2.add("2");
arrayList2.add("1");
for (String s : arrayList2) {
 if("1".equals(s)){
  //导致报错
 arrayList2.remove(s);
 }
}

异常信息如下

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)

先看一下ArrayList中Itr类的源码

 private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         * 翻译为游标,下标。认为指针更为准确,指当前数组访问的位置
         * (学过数据库的话应该对游标有点熟悉)
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         * 最近访问节点的前一个下标 如果调用了remove则重置为-1
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         * 期望的修改数量 初始值modcount 如果不等于 抛出异常
         */
        int expectedModCount = modCount;

       //如果当前游标不等于size大小,则存在下一个元素
        public boolean hasNext() {
            return cursor != size();
        }

       //遍历下一个元素
        public E next() {
            //检测并发修改
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                //游标加一
                cursor = i + 1;
                //从第0个开始遍历
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

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

            try {
                //调用list的remove方法
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                //同步modCount值
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                //抛出异常的位置
                throw new ConcurrentModificationException();
        }
    }

同时贴出ArrayList中remove方法

 public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
               //如果元素为空,删除第一个为null的值
                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)
            //调用本地方法进行数组copy
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //帮助GC 如果数组该元素无法访问,但一直有值,导致无法GC,可能有隐患
        //同时size减一
        elementData[--size] = null; // clear to let GC do its work
    }

通过源码:我们来分析之前的代码

List<String> arrayList1 = new ArrayList<String>();
arrayList1.add("1");
arrayList1.add("2");
for (String s : arrayList1) {
 if("1".equals(s)){
  //不报错
 arrayList1.remove(s);
 }
    //System.out.println(S);  实际上只会打印1
}
List<String> arrayList2 = new ArrayList<String>();
arrayList2.add("2");
arrayList2.add("1");
for (String s : arrayList2) { //第三次循环报错位置
 if("1".equals(s)){
  //导致报错的原因
 arrayList2.remove(s);
 }
}

arrayList1遍历时,第一次遍历进入Itr.next() 方法,cursor =0 +1,在remove(“1”)后,数组size-1=1,在进行第二次遍历时,比较cursor != size()为false,所以跳出循环,实际上未遍历到第二个元素。

arrayList2遍历时,第一次遍历后,cursor = 1,进入第二次遍历,cursor = 2,同时remove第二个元素,这时数组size减为1,modCount加一,进入第三次遍历,比较cursor != size()为true。进入到Itr.next()方法。modCount = 3,expectedModCount=2,报出异常。

总结:在foreach不要进行ArrayList元素的增删改

foreach是把元素转为Iterator对象进行快速遍历的,需要遍历删除元素应该使用iterator对象进行删除。所以可以采用下面这种方式:

Iterator<String> iterator = arrayList2.iterator();
while (iterator.hasNext()) {
	String item = iterator.next();
	if ("1".equals(item)) {
		iterator.remove();
	}
}
发布了1 篇原创文章 · 获赞 0 · 访问量 115

猜你喜欢

转载自blog.csdn.net/sinat_22143835/article/details/104017319