0

I am relying on list iterators to move through a list of characters. This is a single-threaded program and I use listIterator objects sequentially in 4 different methods. Each method has the same setup:

private void myMethod(ArrayList<Integer> input) {
    ListIterator<Integer> i = input.listIterator();
    while (i.hasNext()) {
        Integer in = i.next();
        if (in < 10)
            i.remove();
        else
            i.set(in*in); // because its lucky
    }
}

With this pattern, on the second iterator the following Exception is thrown:

java.util.ConcurrentModificationException

However, looking at the javadocs I don't see this Exception in the Exceptions thrown nor do I see a method to close the iterator after I am done. Am I using the listIterator incorrectly? I have to iterate over the same ArrayList multiple times, each time conditionally removing or mutating each element. Maybe there is a better way to iterate over the ArrayList and this use-case is not best solved by a ListIterator.

java docs for ListIterator

Anthony O
  • 622
  • 7
  • 26

3 Answers3

2

This is explained in the ArrayList javadoc, you are modifying the list with remove() and set() while using an Iterator:

The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
  • So then using multiple iterators is not the right way to solve this problem or I am using the ListIterator interface incorrectly? It seems that even though I have created a reference to the iterators in local variable scope with input.listIterator(), state persists outside of the method call that causes a ConcurrentModificationException to be thrown. – Anthony O Nov 03 '19 at 23:43
  • Your code doesn't compile. What is `c`, there is no method `set(int)`. Fix the code first and explain what is your goal, then we can see if `listIterator()` makes sense in your case. – Karol Dowbecki Nov 03 '19 at 23:48
  • Oh, my bad, I meant to call set() and remove() on the listIterator. But, as I said above "I have to iterate over the same ArrayList multiple times, each time conditionally removing or mutating each element." – Anthony O Nov 03 '19 at 23:53
1

It’s hard to give diagnostic for a problem when the shown code clearly isn’t the code that produced the exception, as it doesn’t even compile. The remove method of Iterator doesn’t take arguments and the set method is defined on ListIterator, but your code declares the variable i only as Iterator.

A fixed version

private void myMethod(ArrayList<Integer> input) {
    ListIterator<Integer> i = input.listIterator();
    while (i.hasNext()) {
        Integer in = i.next();
        if (in < 10)
            i.remove();
        else
            i.set(in*in);
    }
}

would run without problems. The answer to your general question is that each modification invalidates all existing iterators, except the one used to make the modification when you did use an iterator for the modification and not the collection interface directly.

But in your code, there is only one iterator, which is only created and used for this one operation. As long as there is no overlapping use of iterators to the same collection, there is no problem with the invalidation. Iterators existing from previous operations are abandoned anyway and the iterators used in subsequent operations do not exist yet.

Still, it’s easier to use

private void myMethod(ArrayList<Integer> input) {
    input.removeIf(in -> in < 10);
    input.replaceAll(in -> in*in);
}

instead. Unlike the original code, this does two iterations, but as explained in this answer, removeIf will be actually faster than iterator based removal in those cases, where performance really matters.

But still, the problem persists. The shown code can’t cause a ConcurrentModificationException, so your actual problem is somewhere else and may still be present, regardless of how this one method has been implemented.

Holger
  • 285,553
  • 42
  • 434
  • 765
0

I am not knowledgable enough about Java ListIterators to answer the question but it appears I have run into the XY problem here. The problem seems to be better solved with Java Streams to remove the element or map the element into a new ArrayList by exercising a function on each element in the original ArrayList.

    private ArrayList<Integer> myMethod(ArrayList<Integer> input) {
        ArrayList<Integer> results = input.stream().filter(
            in -> (in < 10)).collect(Collectors.toCollection(ArrayList::new));

        results = input.stream().map(
            in -> in*in).collect(Collectors.toCollection(ArrayList::new));

        return results;
    }
Anthony O
  • 622
  • 7
  • 26