17

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.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
JolonB
  • 415
  • 5
  • 25
  • 2
    @pvpkiran I don't think that's my issue. I don't explicitly use any iterator (although I'm sure `reverse` implicitly uses one to reverse the order). Any changes that are being made happen *after* each step has ended (as in, not during iteration) – JolonB Sep 30 '19 at 10:22
  • 1
    error is accord because of this line :- list.removeAll(toReverse); . In debugger see the value of reverse – SSP Sep 30 '19 at 10:27
  • See also https://stackoverflow.com/questions/602636/why-is-a-concurrentmodificationexception-thrown-and-how-to-debug-it – Raedwald Sep 30 '19 at 10:34
  • See also https://stackoverflow.com/questions/8817608/concurrentmodificationexception-thrown-by-sublist – Raedwald Sep 30 '19 at 10:35
  • 1
    Are you aware that `subList` returns a *view* into the original list? When you did `Collections.reverse(toReverse);`, you already modified the original list; the subsequent `list.removeAll(toReverse); list.addAll(startIndex, toReverse);` are entirely obsolete. – Holger Sep 30 '19 at 17:02

4 Answers4

12

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));
helospark
  • 1,420
  • 9
  • 12
  • 1
    There is no need for the copy. As you said yourself, the sublist is a view, so after `Collections.reverse(toReverse);`, the original list has been modified already, so the simple fix is to remove the obsolete `list.removeAll(toReverse); list.addAll(startIndex, toReverse);` statements. – Holger Sep 30 '19 at 17:04
5

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.

Nir Levy
  • 12,750
  • 3
  • 21
  • 38
2

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);
Nikolai Shevchenko
  • 7,083
  • 8
  • 33
  • 42
0

From suggestions of helospark & Nir Levy, use skip & limit in Stream

List<Integer> toReverse = list.stream() //
                .skip(startIndex) //
                .limit(endIndex + 1) //
                .collect(Collectors.toList());
bathudaide
  • 737
  • 1
  • 4
  • 12