在学习集合的时候,我们知道:在使用迭代器遍历集合的时候不能删除集合元素,否则系统就会抛出异常。但是我无意中发现:删除集合的倒数第二个元素不会抛出异常
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Test {
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add("hello");
collection.add("15");
collection.add("world");
collection.add("17");
for (Object obj : collection) {
String str = (String)obj;
System.out.println(str);
if (str.equals("15")) {
collection.remove(str);
}
}
Iterator iterator = collection.iterator();
iterator.next();
System.out.println(collection);
}
}
此时系统就会抛出异常
但是如果当删除的元素是倒数第二个的时候
即:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Test {
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add("hello");
collection.add("15");
collection.add("world");
collection.add("17");
for (Object obj : collection) {
String str = (String)obj;
System.out.println(str);
if (str.equals("world")) {
collection.remove(str);
}
}
Iterator iterator = collection.iterator();
iterator.next();
System.out.println(collection);
}
}
此时系统并不会抛出异常,而是顺利运行
同时呢,我们还可以发现,这个集合的末位元素并没有被遍历
那么我们如何来看待这个问题,首先,我们来了解一些概念:
forEach循环,其实就是根据集合对象创建一个Iterator的迭代对象(又叫做迭代器),用这个迭代对象来遍历集合。
每次forEach循环时都会进行下面的两个操作:
Iterator.hasnext(); // 判断是否有下个元素
obj = iterator.next() // 下个元素是什么,赋值给obj
让我们来看一下对应部分的源码,看看报异常这个部分是如何实现的:
public boolean hasNext() {
return this.cursor != ArrayList.this.size;
}
public E next() {
this.checkForComodification();
int i = this.cursor;
if (i >= ArrayList.this.size) {
throw new NoSuchElementException();
} else {
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
} else {
this.cursor = i + 1;
return elementData[this.lastRet = i];
}
}
}
final void checkForComodification() {
if (ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
}
我们发现,正是这句判断导致系统抛出异常
if (ArrayList.this.modCount != this.expectedModCount) {
throw new ConcurrentModificationException();
}
原因分析:当我们用foreach循环的方式遍历元素的时候,会生成Iterator迭代器,然后使用Iterator遍历。在生成Iterator的时候,会保存一个expectedModCount参数,这个是生成Iterator的时候希望集合中可以修改元素的次数。如果你在遍历过程中删除元素,集合中modCount就会变化
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
}
而当用迭代器删除的时候:使用迭代器删除的时候对expectModCount做了重新赋值;
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // 处理expectedModCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}