前言
这篇文章我们讨论2个问题:
1、List集合通过foreach方式遍历的时候调用remove方法跑出异常的原因
2、List集合中存在相同的数据时,调用revove方法删除数据时的结果分析
正文
foreach遍历中调用remove方法抛异常
先看下面的代码
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
for (String str: list){
System.out.println(str);
list.remove(str);
}
运行后的结果抛出异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
通过debug的运行,我们通过跟踪代码发现,在第一次revove方法被调用的时候,是正常的,当第二次revove的时候会跑出异常,通过异常信息可以看出,是调用了迭代器中的checkForComodification,校验迭代器中的expectedModCount 与modCount不相等,从而抛出异常,那么会有一个问题,foreach的方式,为啥会调用迭代器呢,我们带着这个问题,查看下编译后的代码,如下图所示
List<String> list = new ArrayList();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
Iterator var4 = list.iterator();
while(var4.hasNext()) {
String str = (String)var4.next();
System.out.println(str);
list.remove(str);
}
通过上面编译文件发现,jvm将foreanch的方式,编译成迭代器的方式,来遍历,而 expectedModCount变量是迭代器中的,expectedModCount变量值在初始化的时候是等于modCount,而我们调用list.remove方法的时候,remove方法只会修改modCount的数量,不会修改迭代器中的expectedModCount值,所以在校验的时候,会导致2着不一致。
那么如果结果这个问题??
我们如果想在遍历中删除,可以采用迭代器遍历,调用迭代器中的remove方法,如下面源码
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();
}
}
从源码中可以看出先调用List的remove方法,删除元素,然后将modCount值再次赋值给expectedModCount,从而在调用revove进行比较的时候,二者的值就是相同的。
List集合存在相同数据,调用remove删除的结果分析
先举个例子,如下
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("A");
list.remove("A");
for (int i = 0;i<list.size();i++){
System.out.println(list.get(i));
}
在list中存在2个相同的A元素,那么我们在调用revove方法的时候,结果是2个A都被删除,还是说只会删除一个,如果是删除一个,那么是删除第一个还是第二个呢?
有句话叫实践出真理,在这里叫源码出真理,我们看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;
}
因为我们入参不是null,所以做else逻辑,逻辑中从下标0开始遍历,匹配元素是否等于o,如果是,那么调用 fastRemove(index)方法返回。
所以从源码中看出,当匹配到一个元素后不会再匹配下一个,所以只会删除一个,而且是第一个元素,我们检验是否和分析的结果一致
B
C
D
A
没问题