为什么列表的反向子列表的 List.addAll 会导致 ConcurrentModificationException
Why does List.addAll of a reversed subList of the list cause a ConcurrentModificationException
我一直在尝试将 sub list of a list, reverse it, and place the reversed 列表放回起始位置。例如,假设我们有列表 [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);
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)
错误所指的实际行是list.addAll(startIndex, toReverse);
。
我不确定问题出在哪里,因为在迭代时似乎没有任何变化。如果有人能解释 为什么 会发生这种情况 and/or 如何 修复它,将不胜感激。
根据 helospark & Nir Levy 的建议,在 Stream
中使用 skip & limit
List<Integer> toReverse = list.stream() //
.skip(startIndex) //
.limit(endIndex + 1) //
.collect(Collectors.toList());
List.subList returns 指定元素之间列表的实时视图,而不是那些元素的副本(参见 documentation),因此添加到原始列表也会修改子列表,这将导致 ConcurrentModificationException
(因为正在添加的内容和您添加的内容也会同时修改)。
list.subList(startIndex, endIndex+1)
您可以通过复制列表来修复代码,例如
List<Integer> toReverse = new ArrayList<>(list.subList(startIndex, endIndex+1));
来自 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
因此,当您尝试在子列表 'view' 的索引处添加项目时,它会创建并发修改。
问题出在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);
我一直在尝试将 sub list of a list, reverse it, and place the reversed 列表放回起始位置。例如,假设我们有列表 [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);
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)
错误所指的实际行是list.addAll(startIndex, toReverse);
。
我不确定问题出在哪里,因为在迭代时似乎没有任何变化。如果有人能解释 为什么 会发生这种情况 and/or 如何 修复它,将不胜感激。
根据 helospark & Nir Levy 的建议,在 Stream
中使用 skip & limitList<Integer> toReverse = list.stream() //
.skip(startIndex) //
.limit(endIndex + 1) //
.collect(Collectors.toList());
List.subList returns 指定元素之间列表的实时视图,而不是那些元素的副本(参见 documentation),因此添加到原始列表也会修改子列表,这将导致 ConcurrentModificationException
(因为正在添加的内容和您添加的内容也会同时修改)。
list.subList(startIndex, endIndex+1)
您可以通过复制列表来修复代码,例如
List<Integer> toReverse = new ArrayList<>(list.subList(startIndex, endIndex+1));
来自 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
因此,当您尝试在子列表 'view' 的索引处添加项目时,它会创建并发修改。
问题出在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);