Почему List.addAll обратного подсписка списка вызывает исключение ConcurrentModificationException
Я пытался взять подсписок списка, перевернуть его и поместить перевернутый список обратно в исходное положение. Например, скажем, у нас есть список [1, 2, 3, 4, 5, 6]
, тогда переход от индекса 2 к индексу 4 даст [1, 2, 5, 4, 3, 6]
.
Я написал некоторый код для этого, однако он каждый раз выдает ConcurrentModificationException
(если только startIndex == endIndex). Минимальный воспроизводимый пример приведен ниже:
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);
Исключение в потоке "main" java.util.ConcurrentModificationException
at java.util.ArrayList $ SubList.checkForComodification (неизвестный источник)
на java.util.ArrayList $ SubList.size (неизвестный источник) на
java.util.AbstractCollection.toArray (Неизвестный источник) в
java.util.ArrayList.addAll (неизвестный источник) на
test.ConcurrentExample.main(ConcurrentExample.java:64)
Фактическая строка, на которую ссылается ошибка, - list.addAll(startIndex, toReverse);
.
Я не уверен, в чем проблема, так как кажется, что ничего не меняется во время итерации. Если бы кто-нибудь мог объяснить, почему это происходит и/или как это исправить, это будет с благодарностью.
Ответы
Ответ 1
List.subList возвращает представление в реальном времени списка между указанными элементами, а не копию этих элементов (см. документацию), поэтому добавление в исходный список также изменит подсписок, что приведет к ConcurrentModificationException
(поскольку то, что добавляется, и то, к чему вы добавляете, также изменяются одновременно).
list.subList(startIndex, endIndex+1)
Вы можете исправить код, скопировав список, например
List<Integer> toReverse = new ArrayList<>(list.subList(startIndex, endIndex+1));
Ответ 2
из документации ArrayList.subList :
Возвращенный список поддерживается этим списком, поэтому неструктурные изменения в возвращенный список отражается в этом списке, и наоборот
Поэтому, когда вы пытаетесь добавить элементы в индекс подсписка 'view', это создает одновременную модификацию.
Ответ 3
Проблема лежит здесь в ArrayList#checkForComodification
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
}
Однако в данном конкретном случае вам не нужно повторно добавлять перевернутый подсписок вручную, поскольку реверсия выполняется в исходном списке. Так что все, что вам нужно, это просто бросить
list.removeAll(...);
list.addAll(...);
и оставьте только этот код:
List<Integer> toReverse = list.subList(startIndex, endIndex+1);
Collections.reverse(toReverse);
Ответ 4
Из предложений helospark & Нир Леви, используйте Пропустить & ограничение в потоке
List<Integer> toReverse = list.stream() //
.skip(startIndex) //
.limit(endIndex + 1) //
.collect(Collectors.toList());