
作者 | 杨遥
经常会有对元素增删的场景,但是你真的用对了吗。阿里规范有一条不要在 foreach 循环里进行元素的 remove/add的规范,为什么会这样呢,我们源码分析一波:
场景一foreach删除
可以看到当条件为"1".equals(item)元素被正常删除,但是把删除的条件换成"2".equals(item)的时候,结果却是
Exception in thread "main" java.util.ConcurrentModificationException
List<String> list = new ArrayList<>();list.add("1");list.add("2");for (String item : list) {if ("1".equals(item)) {list.remove(item);}}
为什么会这样呢,我们看一下源码分析,在list进行循环的时候,会首先进入next方法,然后进入next的checkForComodification()方法,checkForComodification,主要是检查modCount和expectedModCount的值是否一致,不一致则抛出异常,这两个初始值都为list.size()也就是2,但是随着元素的修改和增加modCount会进行自增操作,接着到了满足条件移除元素,断点走到了remove(Object o)这个方法,然后走到 fastRemove(index),方法首先就是modCount++,然后进行System.arraycopy将list重新赋值。
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();}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}
list元素移除前后对比,原来有元素1和2,移除后2往前挪了一个位置,第二个位置赋值为null让GC回收
原来的集合:

移除后的集合:

这下应该知道为什么条件为“1”的时候删除不报错,为“2”报错了吧,当条件是1的时候list移除了,集合的size已经变成了1了,压根就没进入第二次循环,所以当你在foreach调用删除元素的时候,可能你的集合根本没有遍历完所有的元素
迭代器删除
List<String> list = new ArrayList<>();list.add("1");list.add("2");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String item = iterator.next();if ("1".equals(item)) {iterator.remove();}}
推荐使用迭代器删除, iterator.remove()删除的源码,可以看到每次remove元素之后,虽然modCount++了,但是可以在源码里面看到这么一句,expectedModCount = modCount;expectedModCount重新赋值了,下次再检查的时候,
也就不会再抛出ConcurrentModificationException了
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();}}




