Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4536

Avoiding the ConcurrentModificationException in Java

$
0
0

1. Introduction

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

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

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

@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

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

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 (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.

3.2. Not Removing During Iteration

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:

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()

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:

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

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.

4. Conclusion

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.


Viewing all articles
Browse latest Browse all 4536

Trending Articles