一. 第一时间想到的错误方式
public static List<String> wrongDelete(List<String> list){
for (String s:list){
if ("a".equals(s)){
list.remove(s);
}
}
return list;
}
然后就抛出了 并发修改异常。
看报错提示位置:
因为 foreach循环在实际执行时,其实使用的是Iterator的hasnext()和next()。
上面的代码实际执行相当于下面的:
public static List<String> wrongDelete2(List<String> list) {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("a".equals(s)) {
list.remove(s);
}
}
return list;
}
调用next()方法获取下一个元素时,第一行代码就是调用了checkForComodification();,而该方法的核心逻辑就是比较modCount和expectedModCount这2个变量的值。
刚开始,我们往List里面add元素时,每次add,modCount++,所以在上面的示例中,刚开始modCount和expectedModCount的值都为3.
但是当执行完下面的一行代码:
list.remove(s);
modCount的值就被修改成了4。所以在第2次获取元素时,modCount和expectedModCount的值就不相等了,所以抛出了java.util.ConcurrentModificationException异常。
这里需要注意一点的是:如果要删除的元素是"b",则可以成功删除,不会报错.
public static List<String> wrongDelete(List<String> list) {
for (String s : list) {
if ("b".equals(s)) {
list.remove(s);
}
}
return list;
}
这里为啥又不报错呢?关键在于hasNext()的判断条件,回归源码:
上表列出了foreach每次循环完关键属性的值的变化,可以看到:当第二次循环完后,cursor == size,然后循环结束,不会在进入next(),从而判断 expectedCount == modCount ?,就不会报异常了.
使用普通for循环也是一样的,因为底层都会去调用Iterator的hasNext()和next()。
二. 如何解决呢?
主要有以下3种方法:
- 使用Iterator的remove()方法
public static List<String> rightDelete(List<String> list) {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("a".equals(s)) {
it.remove();
}
}
return list;
}
可以看出,每次删除一个元素,都会将modCount的值重新赋值给expectedModCount,这样2个变量就相等了,不会触发java.util.ConcurrentModificationException异常。
- 使用for循环正序遍历
public static List<String> rightDelete2(List<String> list) {
for (int i = 0;i < list.size();i++) {
String s = list.get(i);
if ("a".equals(s)) {
list.remove(i);
i = i - 1;
}
}
return list;
}
这种实现方式比较好理解,就是通过数组的下标来删除,不过有个注意事项就是删除元素后,要修正下下标的值.
- 使用for循环倒序遍历
public static List<String> rightDelete3(List<String> list) {
for (int i = list.size() - 1;i >= 0;i--) {
String s = list.get(i);
if ("a".equals(s)) {
list.remove(i);
}
}
return list;
}