阿里巴巴Java开发手册中有这样一条规定:
【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
那么,如果在foreach循环里进行元素的remove/add操作,会发生什么呢?我们来试试看!
运行下列代码:
import java.util.ArrayList;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("Allen");
list.add("Bob");
list.add("Edward");
for (String s : list) {
if (s.equals("Edward")) {
list.remove(s);
}
}
}
}
结果如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at ListTest.main(ListTest.java:11)
Process finished with exit code 1
结果确实不可思议,众所周知,foreach只是Java中的一个语法糖,我们来看看把class文件反编译后真正运行的代码是什么:
public class ListTest {
public ListTest() {
}
public static void main(String[] args) {
List list = new ArrayList();
list.add("Allen");
list.add("Bob");
list.add("Edward");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String s = (String)var2.next();
if (s.equals("Edward")) {
list.remove(s);
}
}
System.out.println(list);
}
}
可以看到,针对Collection的foreach会被转化为while+Iterator来实现,为了搞清楚异常发生的原因,我们深入到ArrayList的Iterator中去一探究竟。
public Iterator iterator() {
return new Itr();
}
ArrayList中的iterator方法返回的是其内部类Itr的实例,我们看Itr的部分方法:
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
...
}
不难发现,在next方法的开始位置就要执行一个checkForComodification检查,而正是这个检查抛出了ConcurrentModificationException异常。checkForComodification检查的是modCount和expectedModCount是否相等,顾名思义,modCount代表对该集合的修改次数,即,如果在Iterator遍历过程中由其它未知因素对集合进行了修改,那么将会发生并发修改异常。
那么,是哪里对modCount进行了修改呢?我们来看ArrayList的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;
}
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
}
不难发现,在fastRemove中对modCount进行了自增操作,从而导致在Iteraor自检时modCount和expectedModCount不等产生了ConcurrentModificationException异常。
那如果我们想对集合进行条件删除,该怎么做呢?推荐两种方法。
方法1:使用Iterator的remove方法
我们之前是使用的List的remove方法导致产生了异常,而使用Iterator的remove方法就不会产生问题,因为Iterator的remove方法统一了modCount和expectedModCount的修改。
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
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();
}
}
}
方法2:使用集合类自带的removeIf方法
只需传入一个谓词,就可完成遍历+条件删除,很方便。
import java.util.ArrayList;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("Allen");
list.add("Bob");
list.add("Edward");
System.out.println(list);
list.removeIf(s -> s.equals("Edward"));
System.out.println(list);
}
}
每日学习笔记,写于2020-05-31 星期日