Avoiding the ConcurrentModificationException in Java – 避免Java中的ConcurrentModificationException

最后修改: 2017年 2月 8日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this article, we’ll take a look at the ConcurrentModificationException class.

在这篇文章中,我们将看一下ConcurrentModificationException类。

First, we’ll give an explanation how it works, and then prove it by using a test for triggering it.

首先,我们将给出一个解释,它是如何工作的,然后通过使用触发它的测试来证明它。

Finally, we’ll try out some workarounds by using practical examples.

最后,我们将通过实际例子尝试一些变通方法。

2. Triggering a ConcurrentModificationException

2.触发一个ConcurrentModificationException

Essentially, the ConcurrentModificationException is used to fail-fast when something we are iterating on is modified. Let’s prove this with a simple test:

本质上,ConcurrentModificationException是用来当我们正在迭代的东西被修改时快速失败的。让我们用一个简单的测试来证明这一点。

@Test(expected = ConcurrentModificationException.class)
public void whilstRemovingDuringIteration_shouldThrowException() throws InterruptedException {

    List<Integer> integers = newArrayList(1, 2, 3);

    for (Integer integer : integers) {
        integers.remove(1);
    }
}

As we can see, before finishing our iteration we are removing an element. That’s what triggers the exception.

我们可以看到,在完成我们的迭代之前,我们正在删除一个元素。这就是触发异常的原因。

3. Solutions

3.解决方案

Sometimes, we might actually want to remove elements from a collection whilst iterating. If this is the case, then there are some solutions.

有时,我们可能真的想在迭代的同时从一个集合中移除元素。如果是这种情况,那么有一些解决方案。

3.1. Using an Iterator Directly

3.1.直接使用迭代器

A for-each loop uses an Iterator behind the scenes but is less verbose. However, if we refactored our previous test to use an Iterator, we will have access to additional methods, such as remove(). Let’s try using this method to modify our list instead:

for-each 循环在幕后使用Iterator,但不那么冗长。然而,如果我们将之前的测试重构为使用Iterator,我们将可以使用额外的方法,例如remove()。让我们尝试使用这个方法来修改我们的列表。

for (Iterator<Integer> iterator = integers.iterator(); iterator.hasNext();) {
    Integer integer = iterator.next();
    if(integer == 2) {
        iterator.remove();
    }
}

Now we will notice that there is no exception. The reason for this is that the remove() method does not cause a ConcurrentModificationException. It is safe to call while iterating.

现在我们会注意到,没有任何异常。原因是remove() 方法不会引起ConcurrentModificationException。它可以在迭代时安全地调用。

3.2. Not Removing During Iteration

3.2.迭代过程中不删除

If we want to keep our for-each loop, then we can. It’s just that we need to wait until after iterating before we remove the elements. Let’s try this out by adding what we want to remove to a toRemove list as we iterate:

如果我们想保留我们的for-each循环,那么我们可以。只是我们需要等到迭代之后再删除元素。让我们尝试一下,在我们迭代的过程中,将我们想要删除的内容添加到toRemove列表中。

List<Integer> integers = newArrayList(1, 2, 3);
List<Integer> toRemove = newArrayList();

for (Integer integer : integers) {
    if(integer == 2) {
        toRemove.add(integer);
    }
}
integers.removeAll(toRemove);

assertThat(integers).containsExactly(1, 3);

This is another effective way of getting around the problem.

这是另一个有效的方法。

3.3. Using removeIf()

3.3.使用removeIf()

Java 8 introduced the removeIf() method to the Collection interface. This means that if we are working with it, we can use ideas of functional programming to achieve the same results again:

Java 8为Collection接口引入了removeIf()方法。这意味着,如果我们正在使用它,我们可以使用函数式编程的思想来再次实现相同的结果。

List<Integer> integers = newArrayList(1, 2, 3);

integers.removeIf(i -> i == 2);

assertThat(integers).containsExactly(1, 3);

This declarative style offers us the least amount of verbosity. However, depending on the use case, we may find other methods more convenient.

这种声明式的风格为我们提供了最少的言语表达。然而,根据不同的使用情况,我们可能会发现其他方法更方便。

3.4. Filtering Using Streams

3.4.使用流进行过滤

When diving into the world of functional/declarative programming, we can forget about mutating collections, instead, we can focus on elements that should be actually processed:

当潜入函数式/声明式编程的世界时,我们可以忘记突变的集合,相反,我们可以专注于应该被实际处理的元素。

Collection<Integer> integers = newArrayList(1, 2, 3);

List<String> collected = integers
  .stream()
  .filter(i -> i != 2)
  .map(Object::toString)
  .collect(toList());

assertThat(collected).containsExactly("1", "3");

We’ve done the inverse to our previous example, by providing a predicate for determining elements to include, not exclude. The advantage is that we can chain together other functions alongside the removal. In the example, we use a functional map(), but could use even more operations if we want to.

我们做了与之前的例子相反的事情,提供了一个谓词来确定要包括而不是排除的元素。这样做的好处是,我们可以在移除的同时将其他函数串联起来。在这个例子中,我们使用了一个函数map(),但如果我们想的话,可以使用更多的操作。

4. Conclusion

4.结论

In this article we’ve shown problems that you may encounter if you’re removing items from a collection whilst iterating, and also provided some solutions to negate the issue.

在这篇文章中,我们展示了在迭代过程中从集合中删除项目时可能遇到的问题,同时也提供了一些解决方法来否定这个问题。

The implementation of these examples can be found over on GitHub. This is a Maven project, so should be easy to run as is.

这些例子的实现可以在GitHub上找到over。这是一个Maven项目,所以应该很容易按原样运行。