Explain Codes LogoExplain Codes Logo

How to avoid java.util.ConcurrentModificationException when iterating through and removing elements from an ArrayList

java
concurrent-modification-exception
list-iteration
java-best-practices
Anton ShumikhinbyAnton Shumikhin·Dec 4, 2024
TLDR

Prevent ConcurrentModificationException in an ArrayList by using Iterator.remove() method during the iteration process. This allows for the safe removal of elements.

Iterator<String> it = list.iterator(); while(it.hasNext()) { if (it.next().equals("removeMe")) { it.remove(); // "removeMe" shall be removed! } }

Essentially: The remove() method in iterator is the doorkeeper, safeguarding the iteration from ConcurrentModificationException.

Strategies to avoid ConcurrentModificationException

Let's take a quick tour of the strategies for eliminating this annoying exception while dealing with ArrayLists.

Condition-based removal with 'removeIf' (Java 8+)

Run removeIf with a Predicate for condition-based removal. It's like having a housecleaning service eliminating any unwanted elements!

list.removeIf(element -> element.equals("removeMe")); // Condition-based removal, easier than finding Waldo.

The mind-bending reversed iteration

Use a conventional for loop and iterate backwards like a movie played in reverse. This keeps indices happy and healthy.

for (int i = list.size() - 1; i >= 0; i--) { if (list.get(i).equals("removeMe")) { list.remove(i); // Taking a step back for a cleaner removal. } }

Cloning for harm-free iteration

Create a clone, a doppelgänger, of the list for iteration while removing elements from the original list. This leaves the clone unaffected by any of the actions.

ArrayList<String> tempList = new ArrayList<>(list); for (String item : tempList) { if (item.equals("removeMe")) { list.remove(item); // Remove from the original list. The clone is honorably spared. } }

Thread-safe iteration with CopyOnWriteArrayList

This thread-safe ArrayList variant makes a fresh copy of the underlying array with every write operation. Sounds expensive, but worth it!

CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(list); for (String item : cowList) { if (item.equals("removeMe")) { cowList.remove(item); // Surprise! The original array stays fearless during the iteration. } }

Levelling up with advanced handling of concurrent modifications

A couple of foundational solutions have been enlisted, but there's more to the world of handling ConcurrentModificationException. Let's unpack some alternative ways.

Accessing elements with indices

Avoid the iterating part altogether and directly access elements by their index. Indices can be your secret passageway to avoid the exception beast.

for (int i = 0; i < list.size(); i++) { String item = list.get(i); // Direct access. No exception will touch you here. }

Prior or post modifications: The refactor way

Modify elements either before or after the iteration process. This way, you can keep the logic simple and avoid tripping over modifications.

Breaking down the collection behavior

Ensure that you understand how modifications introduce ripple effects. For instance, removing an element shifts following elements to the left and their indices significantly change.

Last-minute considerations

Before you go out in the battlefield, it's time to gear up with some key reminders:

CopyOnWriteArrayList: A double-edged sword?

CopyOnWriteArrayList has certain performance costs due to the writes requirinng a copy of the underlying array. So, wield this sword cautiously.

Size matters when operating with lists

Remember, with every removal, list size decreases. Always keep an eye on the ever-changing boundaries when using index-based loops.