问题:
ArrayList在使用foreach循环对元素进行删除时,出现了.ConcurrentModificationException异常,删除第一个元素时没有报错,但是删除第二个元素却抛异常?
对于这个问题,我们更常听到的可能是不要再foreach循环中进行元素的remove和add操作。
带着这个问题做了一个简单的测试:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String s : list) {
if (s.equals("1")){//s.equals("2")
list.remove(s);
}
}
}
代码十分简单,在调试时,这里也有一些问题,在debug时不能仅仅关注ArrayList的remove方法,这样你会基本找不到问题所在,你会发现它都已经删除成功了,但是为什么还是抛了异常。
分析:
由于上面的for循环是走迭代器的所以我们需要看iterator相关的方法具体做了什么:
public boolean hasNext() {
//cursor为当前元素的下标,size为当前list的长度(这里是为什么第一个元素不报错的关键点)
return cursor != size;
}
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
}
为什么说上面的代码是第一个元素不报错的关键点呢?
我们根据上面的测试代码顺序走一遍(s.equals(“1”)):
只看这一个可能看不出什么门道,但是和下面的进行对比,就知道为什么报错了:
根据上面的流程我们看看next,为什么抛出异常:
我们根据上面的流程可以知道,modCount和expectedModCount已经不等了,所以抛出了异常。
什么情况下不抛异常:
cursor随着迭代的增加,而size随着remove的操作而减少(在一次迭代中只删除一次的情况下),最终出现相等的情况,导致hasNext判断为false,导致循环退出,而没有执行next判断(检查在next判断中),所以没有报错。
看下面这个例子(ArrayList中有4个元素):
如果我们在cursor为1的位置删除,执行完删除操作后,cursor为2,size为3,这时满足hasNext判断条件,继续执行next,结果抛出异常。
我们在cursor为2的位置删除,执行完删除操作后,cursor为3,size也为3,不满足hasNext的判断条件,不执行next操作,无异常抛出。
综上所述,可以看出在只删除一个元素的情况下,只有在当删除元素后,下一次hasNext判断为false,next操作不执行的情况才不会报错,而满足这个条件的位置也是固定的,就是集合中倒数第二个元素的位置(集合元素大于等于2)。