sidhartha pani
sidhartha pani

Reputation: 663

Throwing ConcurrentModificationException if we add elements in ArrayList at the end while iterating

I am iterating elements of ArrayList using Iteratot.While iterating I am adding an element into the ArrayList at the end. But getting an error : ConcurrentModificationException.

If I change ArrayList to Set and tried to add elements in set at the end during iteration no error is throwing

Test.java

class Test {
  public static void main() {
    ArrayList<Integer> al = new ArrayList<Integer>();
    al.add(20);
    al.add(30);
    al.add(40);

    Iterator<Integer> it = marks.iterator();
    while (it.hasNext()) {
      int value = it.next();
      if (value == 40) al.add(50);
    }
  }
}

Test2.java

class Test2 {
  public static void main() {
    Set<Integer> marks = new HashSet<>();
    marks.add(20);
    marks.add(30);
    marks.add(40);

    Iterator<Integer> it = marks.iterator();
    while (it.hasNext()) {
      int value = it.next();
      if (value == 40) marks.add(50);
    }
  }
}

Upvotes: 0

Views: 5141

Answers (3)

Andy Turner
Andy Turner

Reputation: 140328

It is mere fluke that you don't get a ConcurrentModificationException with HashSet. The reason for that fluke is that you happen to add the element on the last iteration of the loop.

This is far from guaranteed to happen, because HashSet has no ordering guarantees. Try adding or removing some more elements from the list, and you'll find that eventually be able to get a ConcurrentModificationException, because the 40 won't be the last element in the list.

The simplest way to reproduce this is to work with just a single element (since then the ordering of the HashSet is known):

HashSet<Integer> hashSet = new HashSet<>();
hashSet.add(1);
for (int i : hashSet) {
    hashSet.add(2);
}

This won't throw a ConcurrentModificationException.

ArrayList<Integer> list = new ArrayList<>();
list.add(1);
for (int i : list) {
    list.add(2);
}

This will throw a ConcurrentModificationException.


As to the reason why you don't get the exception when adding to a HashSet on the last iteration, look at the source code for the iterator returned by HashSet.iterator():

abstract class HashIterator {
    // ...

    public final boolean hasNext() {
        return next != null;
    }

    // ...
}

So, the hasNext() value is precomputed, based on next; this is set a bit lower down in the code:

    final Node<K,V> nextNode() {
        Node<K,V>[] t;
        Node<K,V> e = next;
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
        if ((next = (current = e).next) == null && (t = table) != null) {
            do {} while (index < t.length && (next = t[index++]) == null);
                                           // ^ Here
        }
        return e;
    }

So, the value of hasNext is determined when the previous element of the iteration is determined.

Since your code is:

Iterator it = marks.iterator();
while (it.hasNext()) {
  int value = it.next();
   if (value == 40)
     marks.add(50);
}

the hasNext() is called after the loop iteration in which the element is added; but next was set to null when it.next() was called - before the call to add - and so hasNext() will return false, and the loop exits without ConcurrentModificationException.


On the other hand, the iterator of ArrayList uses the size of the underlying list to determine the return value of hasNext():

    public boolean hasNext() {
        return cursor != size;
            // ^ current position of the iterator
            //           ^ current size of the list
    }

So this value is "live" with respect to increasing (or decreasing) the size of the underlying list. So, hasNext() would return true after adding the value; and so next() is called. But the very first thing that next() does is to check for comodification:

    public E next() {
        checkForComodification();
        // ...

And thus the change is detected, and a ConcurrentModificationException is thrown.

Note that you wouldn't get a ConcurrentModificationException with ArrayList if you modified it on the last iteration, if you were to add and remove an element (or, rather, to add and remove equal numbers of elements, so that the list's size is the same afterwards).

Upvotes: 1

mcuenez
mcuenez

Reputation: 1619

There is probably a better solution, but this one is working as well. A second list is used and then added to the original one, after the loop has finished.

Main

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<Integer> al = new ArrayList<Integer>();
        List<Integer> toAdd = new ArrayList<Integer>();
        int stepSize = 10;

        al.add(20);
        al.add(30);
        al.add(40);

        Iterator<Integer> it = al.iterator();
        while (it.hasNext()) {
            int value = it.next();
            if (value == al.get(al.size() - 1)){
                toAdd.add(value + stepSize);
            }
        }

        // Add all elements
        al.addAll(toAdd);

        // Print
        al.forEach(System.out::println);
    }
}

In case you only want to append one element based on the last value, as in your example, you could also wait until the loop is finished (if you still need it) and try to add it like this:

al.add(al.get(al.size() - 1) + stepSize);

Output

20
30
40
50

Upvotes: 5

sitakant
sitakant

Reputation: 1878

Yes , This is expected for normal List.

You can use CopyOnWriteArrayList for concurrent activity on List.

For example , click here

Upvotes: 0

Related Questions