List list = new ArrayList();
list.add("wong");
list.add("song");
list.add("zhao");
list.add("lv");
for (String s : list) {
if (s.equals("wong")) {
list.remove(s);
}
}
这段代码在删除除了倒数第二个元素之外的任意元素的时候,会报ConcurrentModificationException异常,而删除倒数第二个元素的元素时,则一切正常。
当时,我对Java的了解不够深入,所以认为list的remove方法是不安全的,删除的时候尽量使用Iterator的remove方法,代码如下:
Iterator it = list.iterator();
for(;it.hasNext();){
String s = it.next();
if(s.equals("wong")){
it.remove();
}
}
这段代码对于删除list中的元素是安全的,这也使得我对Iterator的好感倍增,直到有一天我看到下面的代码:
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (s.equals("wong")) {
list.remove(s);
}
}
没错,这也是安全的,而且用的是list的remove方法,我当时就。。。
不懂就看源码,反正是开源的,先说list的remove方法:
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;
}
可以看出来,list的remove方法最后调用的是fastRemove方法,其代码如下:
private void fastRemove(int index) {
modCount++;//抛出ConcurrentModificationException的罪魁涡首
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
可以看出来:fastRemove方法首先修改了modCount,使其自增,然后移动被删除元素后的元素,最后改变size,gc最后一个元素。
现在,我们回到第一段代码,为什么在删除倒数第二个元素的时候不会报错呢?原因在于Iterator的hasNext方法:
public boolean hasNext() {
return cursor != size();
}
在Iterator使用next方法后,Iterator中的属性cursor(游标)会指向下一个index,也就是最后一个元素的index:size-1,,而当我们使用list的remove方法,成功的修改了modCount,删除了元素,改变了size的大小,使其自减,这个时候再调用hasNext方法,就会返回false。也就是说,当使用第一段代码,删除倒数第二个元素时,虽然成功删除了倒数第二个元素,但因为没有修改Iterator中的cursor属性,导致Iterator认为已经遍历完了list,最终跳出循环,没有调用next方法,所以也就没有抛出ConcurrentModificationException异常。
类似的情况也发生在第三代码中,当使用index遍历,使用list的remove方法删除元素时,fastRemove方法会将list中被删除元素后的元素前移,这会使得游标i指向预期元素的后一个元素,从而使得遍历不完整。但由于这个游标是暴露的,我们可以对其维护,只需要:
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (s.equals("wong")) {
list.remove(s);
i--;
}
}
就可以了。当然,有些时候,我们可能为了性能,将上面的代码写成如下的形式:
int size = list.size();
for (int i = 0; i < size; i++) {
String s = list.get(i);
System.out.println(s);
if (s.equals("wong")) {
list.remove(s);
i--;
size--;//修改此值,避免数组越界
}
}
如果使用这样的写法,请不要忘记修改size的值,否则会发生IndexOutOfBoundsException数组越界的错误。【完】