阿里java开发规范,为什么不要在 foreach 循环里进行元素的 remove/add 操作?
官方案例
阿里只提出了要求以及案例,但并没有给予解释,不知道大家是否有运行一下下面的案例代码呢?
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test6 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
// Iterator<String> iterator = list.iterator();
// while (iterator.hasNext()) {
// String item = iterator.next();
// if ("2".equals(item)) {
// iterator.remove();
// }
// }
for (String item : list) {
if ("2".equals(item)) {
list.remove(item);
}
}
System.out.println(list);
}
}
ConcurrentModificationException异常分析
运行后,会出现ConcurrentModificationException,即并发修改异常
D:\JDK11\bin\java.exe "-javaagent:D:\IntelliJ IDEA 2021.3.1\lib\idea_rt.jar=57539:D:\IntelliJ IDEA 2021.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\Idea
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
at Test6.main(Test6.java:23)
可得两个主要报错
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
由报错信息可得ArrayList类的997行报错,进入可得:
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];
}
可见,是iterator进行next迭代时checkForComodification()方法进行了异常抛出
checkForComodification()
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
要知道具体如何抛出的异常,还得从增强for循环开始分析
增强for是iterator实现的
通过debug,可以看到增强for循环是用iterator实现的,源码如下
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
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];
}
ConcurrentModificationException异常是在迭代时抛出的
上面来自997行的报错,at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
,就是来自这里的ite中的next()方法
而另一个报错,java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
,来自next()方法中的checkForComodification()方法
checkForComodification()
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这个方法很简单,就是比较modCount
和expectedModCount
,不一样,则抛出异常
modCount和expectedModCount是什么?
- modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数,初始值为0。
- expectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。expectedModCount表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。
ArrayList的remove方法怎么执行的?
remove()
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
重点看fastRemove(es, i)方法
fastRemove(Object[] es, int i)
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
重点来了fastRemove()
中对modCount进行了+1操作,但是expectedModCount并没有得到更新,两者值出现了不同,因此抛出异常!
itearator是什么时候进行的checkForComodification()方法,为什么案例中删除1不会出现异常?
回答这个问题,必须清楚iterator迭代器迭代时的具体逻辑
-
初始化iterator
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount;
在foreach初始化时,定义了一个游标
cursor
,从0计数,每次迭代时cursor
+1,同时初始化了expectedModCount
,其值与modcount
同步 -
在迭代之前,会执行
hasNext()
方法,判断是否要进行迭代public boolean hasNext() { return cursor != size; }
如果下标与集合size相等,则不再进行
next()
,直接退出迭代,反之,进行next(),
这就是iterator迭代器退出的核心机制,也是checkForComodification()
是否会执行的重点,下面会详细说明next()
: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; //迭代时游标+1 return (E) elementData[lastRet = i]; }
-
回归正题,案例中,如果删除"1",并不会抛出异常
原因分析:
第一次进入迭代器,进行初始化后
先进行hasnext(),此时游标
cursor
为0,size为2然后进行next(),
cursor
+1,为1当remove元素1时,size-1
fastRemove()
:private void fastRemove(Object[] es, int i) { modCount++; final int newSize; if ((newSize = size - 1) > i) System.arraycopy(es, i + 1, es, i, newSize - i); es[size = newSize] = null; }
进入第二次迭代时,这次不用再初始化
依然是进行hasnext(),此时!
cursor
和size
都为1,直接返回false,退出迭代!也就是说,还没到抛出异常,这迭代就已经退出了,因此不会抛出异常 -
至于为什么删除元素"2"会出现异常,想必不用多解释了
对比之下,Iterator的remove方法是如何实现的?
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();
}
}
第10行,对expectedModCount
和modCount
进行了同步,因此不会出现并发修改异常