Iterating Through a Collection, Avoiding Concurrentmodificationexception When Removing Objects in a Loop

Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop

Iterator.remove() is safe, you can use it like this:

List<String> list = new ArrayList<>();

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
// Iterator<String> iterator = list.iterator();
// while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String string = iterator.next();
if (string.isEmpty()) {
// Remove the current element from the iterator and the list.
iterator.remove();
}
}

Note that Iterator.remove() is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress.

Source: docs.oracle > The Collection Interface


And similarly, if you have a ListIterator and want to add items, you can use ListIterator#add, for the same reason you can use Iterator#remove — it's designed to allow it.


In your case you tried to remove from a list, but the same restriction applies if trying to put into a Map while iterating its content.

How to avoid ConcurrentModificationException while removing elements from `ArrayList` while iterating it?

Use an Iterator and call remove():

Iterator<String> iter = myArrayList.iterator();

while (iter.hasNext()) {
String str = iter.next();

if (someCondition)
iter.remove();
}

ConcurrentModificationException when removing item from a list

ConcurrentModificationException means:

  1. At point in time A, you make an iterator of some collection either by invoking its .iterator() method, or having a for (var x : collection) {} call it for you.
  2. At point in time B, you change the collection (and not via the iterator you made in A's .remove() method), for example by invoking remove or add or clear or retainAll.
  3. At point in time C, you so much at look funny at that iterator: You invoke any of its methods, or you have the for loop do it by hitting the } of its block.

What you need to do is decidedly nontrivial!

Think about it, given an initial list of [A, B, C, D, E]: you'd presumably want that forEachWithIndex method to get run 5 times, regardless of what happens to the list in between: [0, A], [1, B], [2, C], [3, D], and [4, E]. So what should happen if, during the loop for [0, A], you remove C?

There's an argument to be made that the [2, C] event shouldn't happen at all, and, in fact, that the remaining loops should be [1, B], [2, D], and [3, E]. It's because this is so hard to answer that java solved the problem in the iterator() API by simply not allowing you to do it!

Similar question comes up when you call .add("F") during the loop for [0, A]. Should the for loop run the lambda once with arguments [5, F], or not? An open question.

It's up to you to answer this question, and you should document this in excruciating detail. No matter which choice you make it'll be quite difficult!

I think the for loop should include the changes

This is incredibly complicated. Because imagine that the loop for C ends up removing A. That means that your list will first invoke the lambda with arguments [0, A], [1, B], and [2, C], and then, what's the next iteration going to look like? Presumably the only sane answer then is [2, D]. To make this work you need to track all sorts of things - the list's loop code needs to be aware that deletions happened and that therefore it needs to 'down-adjust' (because you can't simply loop from 0 to 'list size', if you did that, the next iteration would be [3, E] and you have skipped D entirely even though it is still in that list.

Make some coffee, dig in, find a whiteboard, sketch this out. Reserve a full day and be aware that the code will be many pages to deal with it all. Make a TON of test cases and describe in detail precisely what you expect to happen for all of these.

Hmm, okay, nevermind. Let's say it should iterate for all elements the original had no matter what changes

This is easier but inefficient. The fix is simple: First make a copy of the list. Then iterate the copy. The copy cannot change (you're the only one with a ref to it), so they can do whatever they want to the underlying list:

XList<T> copy = new XList<T>(this);
int counter = 0;

var iterator = copy.iterator();
while (iterator.hasNext()) consumer.accept(iterator.next(), counter++);

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

Two options:

  • Create a list of values you wish to remove, adding to that list within the loop, then call originalList.removeAll(valuesToRemove) at the end
  • Use the remove() method on the iterator itself. Note that this means you can't use the enhanced for loop.

As an example of the second option, removing any strings with a length greater than 5 from a list:

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
String value = iterator.next();
if (value.length() > 5) {
iterator.remove();
}
}

Getting a ConcurrentModificationException thrown when removing an element from a java.util.List during list iteration?

I believe this is the purpose behind the Iterator.remove() method, to be able to remove an element from the collection while iterating.

For example:

Iterator<String> iter = li.iterator();
while(iter.hasNext()){
if(iter.next().equalsIgnoreCase("str3"))
iter.remove();
}

Removing elements while iterating. removeIf result in ConcurrentModificationException

Don't iterate and removeIf using elements of your iteration. Beside the problem you're experiencing right now, those calls amount to iterating through the entire collection for each element of the collection (so you're still removing from the collection while iterating, which explains the exception!).

removeIf iterates for you, so all you need is a predicate of SomeObject:

//no loop
someObjectSet.removeIf(ele -> if ele satisfies some condition);

Where ele -> if ele satisfies some condition is the condition that each SomeObject element will be tested against (the ones passing the test will be removed). forEach will orchestrate the test on all elements in someObjectSet, you don't need to do that.


If you're having a secondary condition based on which you want to remove elements, then you can compose predicates (with or), something like in this example:

Set<Integer> set = new HashSet<>(Set.of(1, 2, 3, 4, 5, 6, 7, 8, 9));

Predicate<Integer> predicate = s -> s % 2 == 0;
Predicate<Integer> predicate2 = predicate.or(s -> s % 3 == 0);
set.removeIf(predicate2);

// Test with set.removeIf(predicate);
// then with set.removeIf(predicate2);
// and compare results

ConcurrentModificationException When removing element using list iterator java

After A long debugging, here is the solution.

The sublist function passes by reference and not by value, a sublist created by ArrayList.subList call keeps a reference to the original list and accesses its elementData array directly.

For this reason, when adding an element to the "three" list, we alter the state of the original list. this happens here:

three.add("Y" + (count++));

A way of fixing it for this specific case is to create and initialize the "three" list the following way:

                String one = l.get(0);
String two = l.get(1);
List<String> three = new ArrayList<>();
three.add(one);
three.add(two);
three.add("Y" + (count));

This allows us to manipulate our lists without getting Concurrency Exceptions (ConcurrentModificationException). However, if you are manipulating big lists, I would suggest you use another less hardcoded method for list creation.

I will mark this thread as answered and hope it helps people.



Related Topics



Leave a reply



Submit