Search code examples
javalistadditionlistiterator

Java: avoid Concurrent Modification Exception with ListIterator while modifying a List in a loop


Using Java, I have a list to be checked and in case one of the sublists retrieved through a loop meets some condition, it must be replaced on the fly, since in the next loop I must repeat the algorithm on the changed list. The sublist to be replaced and that to add can have different lengths of course.

I am using a ListIterator since adding and removing using normal List methods can't guarantee the result. But even here, a ConcurrentModificationException is thrown at the beginning of the first for loop (at its second iteration) inside the main for loop, and I can't really think of another way to perform the algorithm.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

public class Dummy {
    public static void main(String[] args) {
        List<String> nodes = new ArrayList<>(Arrays.asList(
                "1", "2", "3", "4", "5", "6", "7"));
        ListIterator<String> nodesIterator = nodes.listIterator();
        while (nodesIterator.hasNext()) {
            nodesIterator.next();
            List<String> nodesToCheck = nodes.subList(nodesIterator
                            .previousIndex(),
                    nodesIterator.previousIndex() + 3);
            if (nodesToCheck.equals(Arrays.asList("3", "4", "5"))) {
                List<String> nodesToReplace = new ArrayList<String>(
                        Arrays.asList("11", "11", "00", "11"));
                for (String n : nodesToCheck) {
                    //ConcurrentModificationException thrown here at 
                    // beginning of second iteration
                    nodesIterator.remove();
                    nodesIterator.next();
                }
                for (String n : nodesToReplace) {
                    nodesIterator.add(n);
                }
            }
        }
    }
}

Any help is extremely appreciated, thanks ;)


Solution

  • You're modifying the initial nodes list so it's natural that an exception is thrown. You should iterate over a copy of the nodes list.

    Here is a runnable example that reproduces the problem (I wrote before OP was edited):

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.ListIterator;
    
    public class Main {
    
        public static void main(String[] args) {
            int number = 2;
            List<String> nodes = new ArrayList<>(Arrays.asList("Hello",
                "World!", "How", "Are", "You"));
            ListIterator<String> nodesIterator = nodes.listIterator();
            while (nodesIterator.hasNext()) {
                nodesIterator.next();
                int fromIndex = nodesIterator.previousIndex();
                List<String> nodesToCheck = nodes.subList(fromIndex,fromIndex + number);
                if (nodesToCheck.contains("Hello")) {
                    for (String n : nodesToCheck) { //ConcurrentModificationException 
                        // thrown here at beginning of second iteration
                        nodesIterator.remove();
                        nodesIterator.next();
                    }
                    List<String> nodesToReplace = new ArrayList<>(
                        Arrays.asList("replace"));
                    for (String n : nodesToReplace) {
                        nodesIterator.add(n);
                    }
                }
            }
        }
    }
    

    A quick and dirty way of patching this (which can be necessary in more complex designs) is:

    public class Main {
    
        public static void main(String[] args) {
            int number = 2;
            List<String> nodes = new ArrayList<>(Arrays.asList("Hello", "World!",
                    "How", "Are", "You"));
            boolean change = true;
            while (change) {
                List<String> copy = new ArrayList<>(nodes);
                ListIterator<String> nodesIterator = copy.listIterator();
                while (nodesIterator.hasNext()) {
                    nodesIterator.next();
                    int fromIndex = nodesIterator.previousIndex();
                    List<String> nodesToCheck = nodes.subList(fromIndex, Math.min
                            (fromIndex + number, nodes.size()));
                    if ((nodesToCheck.equals(Arrays.asList("How", "Are")) ||
                            nodesToCheck.equals(Arrays.asList("Hello", "World!")))) {
                        for (String n : nodesToCheck) {
                            //ConcurrentModificationException thrown here
                            // at beginning of second iteration
                            nodesIterator.remove();
                            if (nodesIterator.hasNext()) {
                                nodesIterator.next();
                            }
                        }
                        nodesIterator.previous();
                        List<String> nodesToReplace = new ArrayList<>(Arrays.asList
                                ("replace"));
                        for (String n : nodesToReplace) {
                            nodesIterator.add(n);
                        }
                        nodes = copy;
                        change = true;
                        break;
                    } else change = false;
                }
            }
            System.out.println(Arrays.toString(nodes.toArray()));
        }
    }
    

    Although it can be further simplified. That's a general programming approach to such problems however - while there is something changing rerun through the collection and modify till there is no change.