28

I'm trying to make use of the foreach loop with the arraylist, but when I use it, it gives me error, but when I use normal for loop, it works perfectly, what could be the problem?

The code is here:

for (Pair p2 : R) {
    if ((p2.getFirstElm() == p.getSecondElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), p2.getSecondElm()));
    else if ((p2.getSecondElm() == p.getFirstElm()) && (p2.getFirstElm() != p2.getSecondElm())) 
        R.add(new Pair (p2.getFirstElm(), p.getSecondElm()));

    // else
    // There are no transitive pairs in R.
}

this is the loop that doesnt work, and here is the one that works:

for (int i = 0; i < R.size(); i++) {
    if ((R.get(i).getFirstElm() == p.getSecondElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (p.getFirstElm(), R.get(i).getSecondElm()));
    else if ((R.get(i).getSecondElm() == p.getFirstElm()) && (R.get(i).getFirstElm() != R.get(i).getSecondElm())) 
        R.add(new Pair (R.get(i).getFirstElm(), p.getSecondElm()));
    //else
    //  There are no transitive pairs in R.
}

the error I'm getting when using the foreach loop is:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
    at java.util.AbstractList$Itr.next(Unknown Source)  
    at set.problem.fourth.PoSet.makeTransitive(PoSet.java:145)  
    at set.problem.fourth.PoSet.addToR(PoSet.java:87)
    at set.problem.fourth.PoSetDriver.typicalTesting(PoSetDriver.java:35)
    at set.problem.fourth.PoSetDriver.main(PoSetDriver.java:13)
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
hakuna matata
  • 3,243
  • 13
  • 56
  • 93

3 Answers3

48

Java Collection classes are fail-fast which means that if the Collection will be changed while some thread is traversing over it using iterator, the iterator.next() will throw a ConcurrentModificationException.

This situation can come in case of multithreaded as well as single threaded environment. - www.javacodegeeks.com

You can't modify a List in a for/each loop, which is syntactic sugar around the Iterator as an implementation detail. You can only safely call .remove() when using the Iterator directly.

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. - Java Collections Tutorial

Calling .add() inside the for/each loop modifies the contents, and the Iterator that is used behind the scenes sees this and throws this exception.

A more subtle concern is the that the second way you list, the .size() is increasing every time you .add() so you will end up processing all the things you .add(), this could possibly cause an endless loop depending on what the input data is. I am not sure if this is what you desire.

Solution

I would create another ArrayList and .add() to it all the new things, and then after the loop, use .addAll() on the original ArrayList to combine the two lists together. This will make things explicit in what you are trying to do, that is unless your intention is process all the newly added things as you add them.

2014 Solution:

Always use Immutable collections classes and build new Immutable collection classes instead of trying to modify a single shared one. This is basically what my 2012 answer says but I wanted to make it more explicit.

Guava supports this very well, use ImmutableList.copyOf() to pass around data.

Use Iterables.filter() to filter out stuff into a new ImmutableList, no shared mutable state, means no concurrency problems!

  • 5
    Your statement is false, "You can't modify a List in a for/each loop". You can modify a list in a foreach loop. The only caveat is you have to break out of the loop after modifying the list. – Patrick Jun 30 '15 at 08:45
4

Under the hood, the for-each loop in Java uses an Iterator for traversing the collection (see this article for a detailed explanation.) And the Iterator will throw a ConcurrentModificationException if you modify the collection while iterating over it, see this post.

Community
  • 1
  • 1
Óscar López
  • 232,561
  • 37
  • 312
  • 386
2

The problem is that you're doing the R.add() in the first line of the loop.

In the first situation you have an iterator open to the arraylist. When you do an add and then try to iterate again the iterator notices that the data structure has changed underneath you.

In the case of the for look you're just getting a new element each time and don't have the concurrent modification issue, though the size is changing as you add more elements.

To fix the problem you probably want to add to a temporary location and add that after the loop or make a copy of the initial data and add to the original.

Paul Rubel
  • 26,632
  • 7
  • 60
  • 80