为什么这段Java代码会触发ConcurrentModificationException?

在循环的第一行中,我收到错误消息,但我不明白为什么。从我读到的,只有当我迭代一个集合并尝试同时修改它时才会发生这种情况,但事实并非如此。

在代码中,list是 类型ArrayList<Product>

void mergeSort() { 
    mergeSort(0, list.size() - 1); //128
}

private void mergeSort(int p, int r) {
    if (p < r) {
        int q = (p + r) / 2;
        mergeSort(p, q); //133
        mergeSort(q + 1, r);
        merge(p, q, r); //135
    }
}

private void merge(int p, int q, int r) {
    List<Product> left = list.subList(p, q);
    left.add(Product.PLUS_INFINITE);
    List<Product> right = list.subList(q + 1, r);
    right.add(Product.PLUS_INFINITE);
    int i = 0;
    int j = 0;
    for (int k = p; k <= r; ++p) {
        Product x = left.get(i).compareTo(right.get(j)) <= 0 ? left.get(i++) : right.get(j++); //147
        list.set(k, x);
    }
}

这是堆栈跟踪:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1415)
    at java.base/java.util.ArrayList$SubList.get(ArrayList.java:1150)
    at ProductList$ProductSort.merge(MainClass.java:147)
    at ProductList$ProductSort.mergeSort(MainClass.java:135)
    at ProductList$ProductSort.mergeSort(MainClass.java:133)
    at ProductList$ProductSort.mergeSort(MainClass.java:128)
    at ProductList.sort(MainClass.java:95)
    at MainClass.main(MainClass.java:187)

回答

subList不会创建与指定范围内的原始列表具有相同元素的新列表。相反,它创建了一个“视图”(docs):

返回此列表部分的视图 [...]。返回列表受此列表支持,因此返回列表中的非结构性更改会反映在此列表中,反之亦然。

另请注意:

如果支持列表(即,此列表)以除返回列表以外的任何方式在结构上进行了修改,则此方法返回的列表的语义将变为未定义。

这正是您在merge. 您正在创建子列表left。然后leftadd. 到现在为止还挺好。但你创造了另一个子表right并修改为好。这使得“ leftto的语义变得未定义”。这会导致下一次调用get抛出异常。

最小可重现示例:

ArrayList<String> list = new ArrayList<>(List.of("1", "2", "3", "4"));
List<String> left = list.subList(0, 2);
List<String> right = list.subList(2, 4);
right.add("5");
left.get(0);

子列表在这方面有点像迭代器(你只能remove通过迭代器,如果你通过原始列表删除,CME可能会被抛出)。

解决此问题的一种简单方法是创建子列表的副本,使它们不再是“视图”,而实际上是独立的列表:

List<Product> left = new ArrayList<>(list.subList(p,q));
List<Product> right = new ArrayList<>(list.subList(q+1,r));


以上是为什么这段Java代码会触发ConcurrentModificationException?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>