为什么当我们删除 for 循环中的元素时并不总是抛出 ConcurentModificationException。



G'day大家好。今天,我遇到了一个关于ConcrentModificationException的非常奇怪的情况。

我有一个这样的清单。

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);

我还有三段类似的代码。

这将打印[2,3]。

for (int i = 0; i < list.size(); i++) {
Integer integer = list.get(i);
if (integer.equals(1)) {
list.remove(integer);
}
}
System.out.println(list);

这将引发异常。

for (Integer integer : list) {
if (integer.equals(1)) {
list.remove(integer);
}
}
System.out.println(list);

这将打印[1,3]。

for (Integer integer : list) {
if (integer.equals(2)) {
list.remove(integer);
}
}
System.out.println(list);

我曾经认为在for循环中删除元素总是会导致ConcurrentModificationException,但我可能错了。你们能告诉我是什么造成了这里的不同吗。我在Corretto 11上运行代码。

这里的重点是,当您通过迭代器访问(!(下一个元素时,而不是在检查是否存在其他元素时,会抛出ConcurrentModificationException。

在第二个片段中,您删除了第一个元素,然后还有两个元素。对于下一次迭代,将注意到有更多的元素将被访问,并且随着预期的ModCount的更改,将抛出Exception。

然而,在第三个示例中,由于大小减小,您将在第二次迭代后到达末尾,不会进行进一步的访问,因此不会引发异常。

第一个例子并不意味着抛出异常,因为您没有使用迭代器,迭代器会首先检查并发修改。

正如前面提到的,第一个代码片段不应该抛出ConcurrentModificationException,因为您没有对列表进行迭代。

关于其他两个片段,等效代码如下:

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
Iterator it = list.iterator();
while(it.hasNext()) {
Integer integer = (Integer)it.next();
if (integer.equals(2)) {
list.remove(integer);
}
}
System.out.println(list);

如果您查看java.util.ArrayList.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];
}

您可以发现这种行为的原因是next((调用中的cursor = i + 1引起的:删除ArrayList中的倒数第二个元素会减少size,随后的hasNext()调用返回false。

常规的for循环和增强的for循环略有不同。

常规的循环遍历列表并逐个获取各个元素,而增强的则使用Iterator。增强的for循环适用于Iterable对象或数组。

基本上,增强的for循环转换为这个

for(Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
Integer integer = it.next();
if (integer.equals(2)) {
list.remove(integer);
}
}

这也解释了第一个增强循环抛出异常的原因。根据迭代器,应该有更多的元素。调用next方法检测到基础集合已更改,因此抛出ConcurrentModificationException

最后一个没有,因为您已经处于迭代器的末尾,因此没有对next的额外调用。

当您删除索引0处的元素时,列表会收缩,因此您的[1,2,3]列表现在是[2,3]。然后删除索引1和3。您的列表中现在有[2]。然后删除已不存在的索引2。

您可能希望在每次迭代时仅移除索引0,或者从最后一个索引移除到第一个索引。

相关内容

  • 没有找到相关文章

最新更新