使用迭代器时出现java.util.ConcurrentModificationException异常

33

我知道如果尝试使用简单的循环从集合中删除元素,就会抛出 java.util.ConcurrentModificationException 异常。但我正在使用迭代器,仍然会生成此异常。有任何想法为什么会这样,以及如何解决?

HashSet<TableRecord> tableRecords = new HashSet<>();

...

    for (Iterator<TableRecord> iterator = tableRecords.iterator(); iterator.hasNext(); ) {
        TableRecord record = iterator.next();
        if (record.getDependency() == null) {
            for (Iterator<TableRecord> dependencyIt = tableRecords.iterator(); dependencyIt.hasNext(); ) {
                TableRecord dependency = dependencyIt.next(); //Here is the line which throws this exception
                if (dependency.getDependency() != null && dependency.getDependency().getId().equals(record.getId())) {
                    tableRecords.remove(record);
                }
            }
        }
    }
4个回答

51
你必须使用 iterator.remove() 而不是 tableRecords.remove()
只有使用迭代器的 remove 方法才能在迭代列表时删除其中的项目。
编辑:
当你创建一个迭代器时,它开始统计已应用于集合的修改次数。如果迭代器检测到某些修改是在没有使用它的方法(或者使用另一个迭代器在同一集合上)的情况下进行的,则不能再保证它不会重复经过相同的元素或跳过其中某个元素,因此会抛出此异常。
这意味着你需要改变你的代码,以便只通过 iterator.remove(并且只有一个迭代器)来移除项目。
或者,在迭代完成后,先制作一个要删除的项目列表,然后再将它们删除。

有两个嵌套的迭代器,所以它可能无法解决这个问题。 - assylias
@assylias 说得对,我没看到第二个。我在这个异常上添加了一些解释。 - Arnaud Denoyelle
1
使用第二个选项创建要删除的项目列表解决了我的问题。谢谢。 - user2219247
这个解决方案对我很有效。我正在使用基于JSONObject的Iterator<String>来递归遍历对象键。当我删除迭代器项时,相关的JSON键也被删除了,这正是我想要的。非常感谢! - tarekahf

3

迭代器快速失败属性会在尝试获取下一个元素时检查底层集合结构的任何修改。如果发现有任何修改,则抛出ConcurrentModificationException异常。除了像ConcurrentHashMap和CopyOnWriteArrayList这样的并发集合类之外,所有Collection类中Iterator的实现都是设计为快速失败的。

来源:Google

下面的示例将帮助您更好地理解:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class IteratorExp {
    public static void main(String... q) {
        //CASE - ONE
        List<String> strList = new ArrayList<>(Arrays.asList("a", "b", "c"));
        Iterator<String> itr = strList.iterator();
        /*
         * strList.add("e"); strList.add("f"); strList.add("g");
         */
        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
        /*
         * Exception in thread "main" java.util.ConcurrentModificationException
         * at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at
         * java.util.ArrayList$Itr.next(Unknown Source) at
         * IteratorExp.main(IteratorExp.java:14)
         */
        
        //CASE - TWO 
        List<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0));
        Iterator<Integer> itrOne = intList.iterator();
        Iterator<Integer> itrTwo = intList.iterator();
        for (; itrOne.hasNext();) {
            if (itrOne.next().equals(5)) {
                itrOne.remove(); // #1
                //intList.remove(itrOne.next()); // #2
            }
        }
        for (; itrTwo.hasNext();) {
            if (itrTwo.next().equals(5)) {
                itrTwo.remove(); // #1
                //intList.remove(itrTwo.next()); // #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
         * IteratorExp.main(IteratorExp.java:35)
         */
    }
}

0
问题在于您同时拥有两个迭代器,并且它们正在“互相斗争”。解决问题的最简单方法就是在找到匹配项时退出内部循环:
for (Iterator<TableRecord> iterator = tableRecords.iterator(); iterator.hasNext(); ) {
    TableRecord record = iterator.next();
    if (record.getDependency() == null) {
        for (Iterator<TableRecord> dependencyIt = tableRecords.iterator(); dependencyIt.hasNext(); ) {
            TableRecord dependency = dependencyIt.next(); //Here is the line which throws this exception
            if (dependency.getDependency() != null && dependency.getDependency().getId().equals(record.getId())) {
                iterator.remove();
                break; // ADD THIS LINE
            }
        }
    }
}

Java的迭代器(Iterator)旨在在其基础容器未使用迭代器进行更改时“快速失败”。由于您正在使用嵌套迭代器,因此对其中一个迭代器发出的任何remove()操作都会导致另一个迭代器在继续使用时抛出异常。因此,如果您需要发出remove()操作,则需要在“外部”迭代器上执行此操作(这是您正在执行的操作),然后停止使用第二个迭代器(添加的break语句实现了这一点)。

0

HashSet的迭代器合同规定,除了通过特定迭代器的remove方法之外,您不能从hashset中删除任何内容。从dependencyIt的角度来看,您已经删除了一个项目,而不是通过调用其remove方法,因此它会抛出ConcurrentModificationException

看起来您想要在记录具有相同记录ID时从hashset中删除记录。重写记录的equalshashcode方法以确保具有相同ID的记录相等并具有相同的哈希码是否更容易?(如果这有意义的话)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接