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

Avoiding ConcurrentModificationException when Iterating and Removing

$
0
0

1. Introduction

When it comes to iterating and modifying collections, ConcurrentModificationException is a common scenario to be thought about. This exception gets thrown by collection iterators in a fail-fast fashion when the underlying collection gets modified while being iterated.

To be precise, this exception can be thrown when calling next() or remove() (Iterator API) and underlying changes are detected, thereby causing the iteration to stop.

2. Detecting Changes

Before trying to understand how to get around it, it is a good idea to know how change detection works. The vast majority of Collection API implementations have a counter for changes (named modifications counter – modCount), each call to modifying methods increments this counter by 1.

When the iterator is initialized, for a given collection, this counter is recorded and used to assert if changes have been made since the iterator creation.

Taking ArrayList as an example, all add, remove and clear methods increments the counter. In this specific, however, the set method doesn’t increment this counter. This means it’s possible to change the ArrayList content with set(int index, E element) without triggering the ConcurrentModificationException while iterating the list.

The example below doesn’t throw an exception but sets the last element of the list to 3:

ArrayList<Object> array = new ArrayList<>(asList(0, "one", 2, "three"));

for (Object item : array) {
    array.set(3, 3);
}

3. How to avoid ConcurrentModificationException

Different approaches can be used to remove elements while iterating a list. Choosing a particular method depends on the particular problem you are trying to solve.

Note that, the methods mentioned below assume the requirement is to remove elements while iterating over the list. Also important to mention that it is not the purpose of this article to ensure thread-safety.

3.1. Use the Iterator API

If we require only to remove an element from a collection and iterate through all elements, one can use the remove() method on the Iterator API:

List<String> originalList = new ArrayList<>(
  asList("zero", "one", "two", "three"));
Iterator<String> iterator = originalList.iterator();

while (iterator.hasNext()) {
    String next = iterator.next();
    if (Objects.equals(next, "one")) iterator.remove();
}

The example above removes the element “one” from the original list.

3.2. Using the ListIterator API

The ListIterator API is an addition on top of the Iterator API. It allows not only to remove elements but also modify and add new elements while iterating over the list of elements:

List<String> originalList = new ArrayList<>(
  asList("zero", "one", "two", "three"));
ListIterator<String> iterator = originalList.listIterator();

while (iterator.hasNext()) {
    String next = iterator.next();
    if (Objects.equals(next, "one")) {
        iterator.set("another");
    }

    if (Objects.equals(next, "two")) {
        iterator.remove();
    }

    if (Objects.equals(next, "three")) {
        iterator.add("four");
    }
}

The previous example will change the original list, leaving it with the following elements: “zero”, “another”, “three”, “four”.

3.3. Iterating Over a Copy

Another option is to remove elements by iterating over a copy of the original list and perform the removal on the original list. When compared to the previous approach, this allows modifying the list other than the current element being accessed.

In the example below, we are removing the previous element, rather than the current one:

List<String> originalList = new ArrayList<>(
  asList("zero", "one", "two", "three"));
List<String> listCopy = new ArrayList<>(originalList);

for (String next : listCopy) {
    if (Objects.equals(next, "one")) {
        originalList.remove(originalList.indexOf(next) - 1);
    }
}

This logic cannot be accomplished with the usage of Iterator neither ListIterator API.

3.4. Using the CopyOnWriteArrayList

This approach uses CopyOnWriteArrayList, compared to the previous approach, this one is cleaner, however less performant, given every write operation on the list will create a copy of the entire underlying collection.

In fact, CopyOnWriteArrayList was designed for thread-safety accounting for scenarios where traversal operations vastly outnumber mutations:

List<String> originalList = new CopyOnWriteArrayList<>(
  asList("zero", "one", "two", "three"));

for (String next : originalList) {
    if (Objects.equals(next, "one")) {
        originalList.remove(originalList.indexOf(next) - 1);
    ]
}

4. Conclusion

Depending on what you are trying to achieve, solutions can vary. Although convenient APIs, Collection, and Iterator have some limitations with ConcurrentModificationException being thrown as one of the most commonly found by developers.

As usual, all the code can be found in our GitHub repository.


Viewing all articles
Browse latest Browse all 4535

Trending Articles