今天看java开发手册(华山版)时发现一个巨坑!!!
话说阿里搞的这个东西真是excited!
手册上是这样描述的:
11. 【强制】不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
正例:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?
上面代码删除List中的“1”时候是没有问题的,但是当我测试他这个说明时
说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?
正例的代码就是正例,没有任何问题;反例就不行了直接报错:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at top.sencom.redis.Demo.RedisDemo.main(RedisDemo.java:22)
那就来仔细研究一下到底怎么回事!
问题首先出现在ava.util.ArrayList$Itr.next
哎,对于反例用的可是foreach循环啊,咋报错中有迭代器字样的东西(Itr),莫非这个foreach循环就是用迭代器实现的???!!!
通过询问度娘得到如下结果:https://blog.csdn.net/a596620989/article/details/6930479
通过这篇文章可以得出:foreach语法最终被编译器转为了对Iterator.next()的调用
这样的话就是来回翻一翻,瞅一瞅
java.util.ArrayList
java.util.AbstractList
于是乎就找到了这个地方:
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
当我们用foreach时迭代器初始时 int expectedModCount = modCount;
这里的modCount该值表示对List的修改次数,它是在父类java.util.AbstractList中定义的,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。
此时你在foreach循环里面执行list.remove(item);
元素是移除了,但是ArrayList 类中的remove()方法会使modeCount++;而foreach循环的迭代器中的expectedModCount
并没有++,这就会使的在迭代器调用next()方法时调用checkForComodification()方法中发现modCount != expectedModCount
从而 throw new ConcurrentModificationException();
好,既然知道了错误出在什么地方;那么就想办法避免这个问题吧。
那就是在使用迭代器时移除元素需要使用迭代器的移除方法而非List的移除方法!
就像正例中那样即可!
其实在JDK中也有类似表述:
The iterators returned by this class’s
iterator
andlistIterator
methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator’s ownremove
oradd
methods, the iterator will throw aConcurrentModificationException
. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html、
在回头来看问什么移除“1”可以?
这还得在分析一下:
“1”这个元素在list的倒数第二个当它被list的remove()方法移除之后
list的size()会减少;而cursor表示当前位置。
迭代器完成一次循环执行 public boolean hasNext() {
return cursor != size();
}
时就会返回fase从而终止循环:也就是说当你往列表中添加两个元素都为“1”时你会发现foreach循环只能删除一个。也就是第一个“1”.