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();
}
}