问题
今天在学习集合的时候说在使用迭代器遍历集合的时候不能删除集合元素否则就会抛出异常;我无意中发现删除倒数第二个元素不会抛出异常
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class ForEachTest {
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 ForEachTest {
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
循环是什么?
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();
}
}
上面是报异常地方的源代码
我们可以看到是因为
ArrayList.this.modCount != this.expectedModCount
具体的原因是:以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
}
那为什么可以使用迭代器删除
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();
}
}
使用迭代器删除的时候对expectModCount
做了重新赋值;
如果删除的元素是倒数第二个数的话,其实是不会报错的。为什么呢,来一起看看。
之前说了foreach循环会走两个方法hasNext() 和next()。如果不想报错的话,只要不进next()方法就好啦;
public boolean hasNext() {
return cursor != size;
}
要求hasNext()的方法返回false了,即cursor == size。其中cursor是Itr类(Iterator子类)中的一个字段,用来保存当前iterator的位置信息,从0开始。cursor本身就是游标的意思,在数据库的操作中用的比较多。只要curosr不等于size就认为存在元素。由于Itr是ArrayList的内部类,因此直接调用了ArrayList的size字段,所以这个字段的值是动态变化的,既然是动态变化的可能就会有问题出现了。