Java - 通过嵌套循环从单个列表中删除,避免并发修改异常

Java - Remove from single list over nested loop avoiding concurrent modification exception

所以我有这个方法应该在一个集合中找到对,为此我使用了一个嵌套循环。但是,即使我使用的是迭代器,我也总是会遇到并发修改异常。我猜想 两个迭代器迭代同一个集合 ,他们都试图同时修改它,这就是我得到这个异常的原因。你能帮我完成同样的结果来避免这个错误吗?

private List<Pair<Document, Document>> createPairDocument(List<Document> documentsToIterate){
       List<Pair<Document, Document>> pairDocList = new ArrayList<>();
       //iterators are used to avoid concurrent modif exception
       Iterator<Document> iterator0 = documents.iterator();
       while(iterator0.hasNext()){
           Document dl0 = iterator0.next();
           Iterator<Document> iterator1 = documents.iterator(); //returns new instance of iterator
           while(iterator1.hasNext()){
               Document dl1 = iterator1.next();
               if (dl1.relatedTo(dl0) && dl0.relatedTo(dl1)){
                   pairDocList.add(Pair.of(dl0, dl1));
                   //these docs should be removed to avoid creating the same relation again
                   iterator0.remove();
                   iterator1.remove();
                   break;
               }
           }
       }
       return pairDocList;
   }

ConcurrentModificationException是因为当一个迭代器在迭代一个集合时,它不知道集合被修改了,所以当集合真正被修改时,迭代器变得很混乱(有一个无效状态) .通过使用 Iterator.remove 方法,您让迭代器知道您正在删除元素,以便迭代器可以相应地调整其状态。

然而,在这种特殊情况下,出现异常是因为 iterator1 没有在 iterator0.remove(); 行中告知 iterator0 刚刚执行的删除操作。当iterator1试图移除它的元素时,它发现它的列表已经改变。

使用两个遍历同一个列表的迭代器不是一个好主意。我认为您可以使用常规的 for 循环遍历列表的索引,并且每次从该索引 + 1 获得一个 list 迭代器 ,因为文档不能与其自身相关。

for (int i = 0 ; i < documentsToIterate.size() ; i++) {
    var iteratorFromI = documentsToIterate.listIterator(i + 1);
    var dl0 = documentsToIterate.get(i);
    while (iteratorFromI.hasNext()) {
        var dl1 = iteratorFromI.next();
        if (dl1.relatedTo(dl0) && dl0.relatedTo(dl1)){
            pairDocList.add(Pair.of(dl0, dl1));
            iteratorFromI.remove();
            documentsToIterate.remove(i);
            i--; // so that the next one doesn't get skipped
            break;
        }
    }
}

现在我们没有并发修改异常,因为我们在 iteratorFromI.remove() 之后做 documentsToIterate.remove(i);,之后我们把迭代器扔掉了,所以它永远不知道我们修改了列表:)

或者,只需使用 2 个常规 for 循环。

pairDocList 切换到 pairDocSet 时,也许您的问题可以轻松解决。

当您制作一组 PairDocuments 时,您不需要从列表中删除任何元素。 add 相同的 PairDocument 两次或更多次到 Set 就可以了,因为 Set 中没有重复项。您必须付出一些努力来识别具有正确 equals()hashCode() 的相同 PairDocuments,但这是值得的。

我也会改进算法,而不是一直检查一个元素所有的元素尝试使用索引并使第二个循环索引(j)基于第一个循环索引(i ).不要进行任何删除并使用集合,以防您认为列表中可能有重复项,如此处所建议的那样。

for (int i = 0; i < documentsToIterate.size() - 1; i++) {
    for (int j = i + 1; j < documentsToIterate.size(); j++) {
        if (related(doc[i],doc[j]);
           addPair(..);
    }
}