无法弄清楚为什么会抛出 ConcurrentModificationException

Cannot figure out why it throws ConcurrentModificationException

我遇到了 ConcurrentModificationException,但不知道为什么。我知道尝试使用 for 循环遍历列表并删除循环块内的元素是个坏主意,并且会引发此类异常,但我不知道如何解决我的问题。

 private static final List<Integer> originalList = new ArrayList<>();

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            originalList.add(i);
        }


        final int MAX_GROUP_SIZE = 5;
        int partitionSize = 4;

        List<List<Integer>> partitions = new LinkedList<>();

        for (int i = 0; i < originalList.size(); i += partitionSize) {
            partitions.add(originalList.subList(i,
                    Math.min(i + partitionSize, originalList.size())));
        }

        int lastGroupSize = partitions.get(partitions.size() - 1).size();

        if (lastGroupSize < partitionSize && partitions.size() > lastGroupSize){
            List<Integer> lastGroup = partitions.remove(partitions.size() - 1);
            for (int i = 0; i < lastGroupSize; i++) {
                partitions.get(i).add(lastGroup.get(i));
            }
        }
        System.out.println("GROUPS: " + partitions.size());
        printGroups(new LinkedList<>(partitions));
    }

你永远不应该迭代列表并在这样做时执行更新操作(更新意味着添加或删除元素)。这是灾难的根源。

为了解决这个问题,可以遵循三种可能的方案:

1) 复制列表,遍历副本并从原始列表中删除。

for (var number : new ArrayList<>(original)) {
    if (element > 10) {
        original.remove(element);
    }
}

2) 使用流

List<Integer> filtered = original.stream()
                                 .filter(i -> i > 10)
                                 .collect(Collectors.toList());

3) 使用迭代器遍历列表

Iterator<Integer> iterator = original.iterator();
while (iterator.hasNex()) {
    Integer number = iterator.next();
    if (number > 10) {
        iterator.remove();
    }
}

我个人更喜欢流。

问题是您对 subList() 的调用不会创建新列表。正如 javadoc 所说:

Returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive.

javadoc 还说:

The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list.

当您调用 partitions.get(i).add(...) 时,您在 结构上修改 originalList,导致错误。

我不相信你是故意的,所以要解决这个问题,你只需要确保你的子列表独立于原始列表,即副本,这很容易做到:

new ArrayList<>(originalList.subList(...))

使用 ArrayList(Collection) 构造函数将创建子列表的副本。

因此,更改此语句:

partitions.add(originalList.subList(i,
        Math.min(i + partitionSize, originalList.size())));

对此:

partitions.add(new ArrayList<>(originalList.subList(i,
        Math.min(i + partitionSize, originalList.size()))));