Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does List.addAll of a reversed subList of the list cause a ConcurrentModificationException [duplicate]

I've been trying to take a sub list of a list, reverse it, and place the reversed list back into the starting position. For example, say we have the list [1, 2, 3, 4, 5, 6], then reversing from index 2 to index 4 would give [1, 2, 5, 4, 3, 6].

I've written some code for this, however it gives a ConcurrentModificationException every time (unless startIndex == endIndex). A minimum reproducible example is provided below:

int startIndex = 2;
int endIndex = 4;
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);

List<Integer> toReverse = list.subList(startIndex, endIndex+1);
Collections.reverse(toReverse);
list.removeAll(toReverse);
list.addAll(startIndex, toReverse);

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(Unknown Source)
at java.util.ArrayList$SubList.size(Unknown Source) at
java.util.AbstractCollection.toArray(Unknown Source) at
java.util.ArrayList.addAll(Unknown Source) at
test.ConcurrentExample.main(ConcurrentExample.java:64)

The actual line the error refers to is list.addAll(startIndex, toReverse);.

I'm not sure what the issue is as nothing appears to be getting changed while being iterated over. If anyone could explain why this is happening and/or how to fix it, that would be greatly appreciated.

like image 710
JolonB Avatar asked Sep 30 '19 10:09

JolonB


3 Answers

List.subList returns a live view of the list between the specified elements, not a copy of those elements (see documentation), therefore adding to the original list will also modify the sublist, which will lead to ConcurrentModificationException (since what is being added and to what you add to also are modified at the same time).

list.subList(startIndex, endIndex+1)

You can fix a code by copying the list, like

List<Integer> toReverse = new ArrayList<>(list.subList(startIndex, endIndex+1));
like image 183
helospark Avatar answered Nov 15 '22 17:11

helospark


from the documentation of ArrayList.subList :

The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa

So when you try to add items at the index of the subList 'view', it creates concurrent modification.

like image 30
Nir Levy Avatar answered Nov 15 '22 15:11

Nir Levy


Issue lies here at ArrayList#checkForComodification

private void checkForComodification() {
    if (ArrayList.this.modCount != this.modCount)
        throw new ConcurrentModificationException();
    }
}

However in this particular case you don't need to manually re-add reversed sublist, since the reversion is performed on the original list. So all you need is just to drop

list.removeAll(...);
list.addAll(...);

and leave only this code:

List<Integer> toReverse = list.subList(startIndex, endIndex+1);
Collections.reverse(toReverse);
like image 25
Nikolai Shevchenko Avatar answered Nov 15 '22 16:11

Nikolai Shevchenko