在阿里巴巴java开发规范中关于集合删除有一项:
11.【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。 正例: List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (删除元素的条件) { 12/44 Java 开发手册 iterator.remove(); } } 反例: for (String item : list) { if ("1".equals(item)) { list.remove(item); } } 说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?
我们可以debug看一下源码:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) 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 remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { modCount++; 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 }
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;
}
/**
* 获取下一个元素
* @return
*/
@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();
}
}
/*
检查改变,判断modCount与expectedModCount是否相等
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
上面是主要涉及到的代码,当使用for循环遍历时,其实是使用Itr去遍历。
Itr通过游标进行遍历,遍历前先调用hasNext()方法判断是否有下一个元素,有则调用next()方法将cursor置为下一个下标。
原示例中删除是调用的ArrayList的remove()方法,删除的时候只是将mod+1。
所以当执行到next()方法中checkForComodification()方法时mod与expectedModCount不相等抛异常:
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.yannic.road.Demo1.main(Demo1.java:12)
那为什么要去执行这个checkForComodification()方法呢?因为ArrayList是线程不安全的,对ArrayList一边遍历一边删除,那么遍历的结果极有可能错误的,所以java有fast-fail机制。当mod与expectedModCount不相等时,说明正在执行删除或添加操作,既然结果最终是错的,不如早点报错抛异常。这是为了多线程设置的一种机制。
使用iterator进行遍历删除:
Iterator iterator = list.iterator(); while (iterator.hasNext()) { if (iterator.next().equals("2")) { iterator.remove(); } }
这里Iterator执行remove()方法是调用的Itr的remove(),再调用完ArrayList的remove()方法会将游标置为上一个下标,并且将expectedModCount = modCount,所以再执行这个checkForComodification()方法就不会抛异常了。