为什么这段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。然后left用add. 到现在为止还挺好。但你创造了另一个子表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));
THE END
二维码