【问题标题】:Why does List.addAll of a reversed subList of the list cause a ConcurrentModificationException [duplicate]为什么列表的反向子列表的 List.addAll 会导致 ConcurrentModificationException [重复]
【发布时间】:2020-01-29 14:48:19
【问题描述】:

我一直在尝试获取列表的sub list,将其反转,然后将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);

线程“主”java.util.ConcurrentModificationException 中的异常
在 java.util.ArrayList$SubList.checkForComodification(未知来源)
在 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);

我不确定问题出在哪里,因为在迭代时似乎没有任何变化。如果有人能解释为什么会发生这种情况和/或如何解决它,我们将不胜感激。

【问题讨论】:

  • @pvpkiran 我不认为这是我的问题。我没有明确使用任何迭代器(尽管我确信reverse 隐含地使用一个来反转顺序)。所做的任何更改都发生在每个步骤结束之后(如在迭代中,而不是在迭代期间)
  • 错误是一致的,因为这一行:- list.removeAll(toReverse); .在调试器中查看 reverse 的值
  • 您是否知道subList 会将view 返回到原始列表中?当你做Collections.reverse(toReverse);时,你已经修改了原来的列表;随后的list.removeAll(toReverse); list.addAll(startIndex, toReverse); 完全过时了。

标签: java arraylist collections concurrentmodification


【解决方案1】:

List.subList 返回指定元素之间列表的实时视图,而不是这些元素的副本(请参阅documentation),因此添加到原始列表也会修改子列表,这将导致ConcurrentModificationException (因为正在添加的内容和您添加的内容也同时被修改)。

list.subList(startIndex, endIndex+1)

您可以通过复制列表来修复代码,例如

List<Integer> toReverse = new ArrayList<>(list.subList(startIndex, endIndex+1));

【讨论】:

  • 不需要复制。正如你自己所说,子列表是一个视图,所以在Collections.reverse(toReverse);之后,原来的列表已经被修改了,所以简单的解决方法是删除过时的list.removeAll(toReverse); list.addAll(startIndex, toReverse);语句。
【解决方案2】:

来自ArrayList.subList 的文档:

返回的列表是由这个列表支持的,所以非结构性的变化 返回的列表反映在这个列表中,反之亦然

因此,当您尝试在子列表“视图”的索引处添加项目时,它会创建并发修改。

【讨论】:

    【解决方案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】:

      根据 helosparkNir Levy 的建议,在 Stream 中使用 skip & limit

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-20
        • 1970-01-01
        • 2015-09-15
        • 2014-06-19
        • 1970-01-01
        • 2013-01-18
        • 2015-10-11
        • 1970-01-01
        相关资源
        最近更新 更多