简体   繁体   中英

Cannot figure out why it throws ConcurrentModificationException

I've got the ConcurrentModificationException and do not know why. I know that trying to iterate through a list using for loops and deleting elements inside the loop block is bad idea and can throw such exception, but I have no idea how to fix it in my case.

 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));
    }

The problem is that your calls to subList() don't create new lists. As the javadoc says it:

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

The javadoc also says:

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

When you call partitions.get(i).add(...) , you're structurally modifying originalList , causing the error.

I don't believe you intended that, so to fix the problem, you just need to make sure your sub-lists are independent of the original list, ie copies, which is easy to do:

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

Using the ArrayList(Collection) constructor will create a copy of the sub-list.

So, change this statement:

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

to this:

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

You should never iterate the list and perform updating actions while doing so (where updating means adding or removing elements). This is a recipe for disaster.

In order to resolve this there are three possible scenarios to follow:

1) Copy the list, iterate over the copy and remove from the original one.

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

2) Use Streams

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

3) Use an iterator to loop over the list

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

Personally I prefer streams.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM