我正在尝试运行以下代码:
List<String> l = new ArrayList<String>();
l.add("jim");
l.add("pam");
for(String s : l) {
l.remove(s);
}
但是我没有任何例外。不是每个循环都使用Iterator,并且我们不能在迭代时更改List的结构吗?
编辑:
当列表包含3个以上元素时,我得到ConcurrentModificationException
。为什么会这样?
答案 0 :(得分:3)
之所以没有ConcurrentModificationException
是因为您的列表仅包含两个元素,这阻止了实现检测到并发修改。这是Iterator
使用的Java 13 ArrayList
实现:
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]; } 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(); } } @Override public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); final int size = ArrayList.this.size; int i = cursor; if (i < size) { final Object[] es = elementData; if (i >= es.length) throw new ConcurrentModificationException(); for (; i < size && modCount == expectedModCount; i++) action.accept(elementAt(es, i)); // update once at end to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
如您所见,它在next()
方法中检查并发修改,但不在hasNext()
方法中检查。这个很重要。如果您不知道,则增强的for循环在幕后使用Iterator
。换句话说,您的for-each循环等效于:
for (Iterator<String> itr = l.itr(); itr.hasNext(); ) {
String e = itr.next();
l.remove(e);
}
一旦循环删除了第一个元素,列表中只剩下一个元素。但是,该元素已经向下移动了一个索引(1→0),但是迭代器的光标增加了一个。这使光标等于大小,因此循环不会第二次执行。由于循环不会第二次执行,因此不会再次调用next()
方法,也不会进行并发修改检查。这样做还有一个副作用,就是将第二个元素保留在列表中。
一旦列表中包含三个或更多元素,循环便会再次执行 ,从而使它可以检测到并发的修改。