11

I am seeing a weird behavior.

    List<String> li = new ArrayList<>();
    li.add("a");
    li.add("b");
    li.add("c");
    li.add("d");
    li.add("e");
    for(String str:li){
        if(str.equalsIgnoreCase("d")){
            li.remove(str);     //removing second last in list works fine
        }
    }

But if i try to remove any other than second last in the list, i get ConcurrentModificationException. It came to my attention while reading "Oracle Certified Associate Java SE 7 Programmer Study Guide 2012" which incorrectly assumes that .remove() always works with an example of removing the second last in the list.

amit bakle
  • 3,321
  • 1
  • 15
  • 14
  • actually i know how to remove an element in array, but i wanted to point out why it works only on the second last element, is this a bug as non-consistent behavior ?? – amit bakle Apr 19 '13 at 09:00

7 Answers7

15

In a list, adding or removing is considered as a modification. In your case you have made 5 modifications(additions).

‘for each’ loop works as follows,

1.It gets the iterator.
2.Checks for hasNext().
public boolean hasNext() 
{
      return cursor != size(); // cursor is zero initially.
}

3.If true, gets the next element using next().

public E next() 
{
        checkForComodification();
        try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
        } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
        }
}

final void checkForComodification() 
{
    // Initially modCount = expectedModCount (our case 5)
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Repeats steps 2 and 3 till hasNext() returns false.

In case if we remove an element from the list , it’s size gets reduced and modCount is increased.

If we remove an element while iterating, modCount != expectedModCount get satisfied and ConcurrentModificationException is thrown.

But removal of second last object is weird. Lets see how it works in your case.

Initially,

cursor = 0 size = 5 --> hasNext() succeeds and next() also succeeds without exception.
cursor = 1 size = 5 --> hasNext() succeeds and next() also succeeds without exception.
cursor = 2 size = 5 --> hasNext() succeeds and next() also succeeds without exception.
cursor = 3 size = 5 --> hasNext() succeeds and next() also succeeds without exception.

In your case as you remove ‘d’ , size gets reduced to 4.

cursor = 4 size = 4 --> hasNext() does not succeed and next() is skipped.

In other cases, ConcurrentModificationException will be thrown as modCount != expectedModCount.

In this case, this check does not take place.

If you try to print your element while iterating, only four entries will be printed. Last element is skipped.

Hope I made clear.

prasanth
  • 3,502
  • 4
  • 28
  • 42
5

Don's use List#remove(Object) here since you are accessing elements from the List in for-each loop.

Instead use Iterator#remove() to remove an item from List:

for(Iterator<String> it=li.iterator(); it.hasNext();) {
    String str = it.next();
    if(str.equalsIgnoreCase("d")) {
        it.remove();     //removing second last in list works fine
    }
}
anubhava
  • 761,203
  • 64
  • 569
  • 643
2

Please use Iterator#remove() method while removing elements from a List while looping . Internally the for-each loop will use the Iterator to loop through the Listand since the behavior of an Iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling Iterator's remove() method.You are getting the exception .

This loop :

for(String str:li){
    if(str.equalsIgnoreCase("d")){
       li.remove(str);     //removing second last in list works fine
     }
}

is basically

Iterator<String> itr = li.iterator();
  while(itr.hasNext()){
    String str = (String)itr.next();
    if(str.equalsIgnoreCase("d")){
        li.remove(str);     //removing second last in list works fine
    }
}

Why removing second last element doesn't throw exception ?

Because by removing the second last element you have reduced the size to the number of elements which you have iterated over. A basic hasNext() implementation is

    public boolean hasNext() {
       return cursor != size;
    }

So in this case the cursor=size=4, so hasNext() evaluates to false and the loop breaks early before the concurrent modification check is performed in next(). The last element is never accessed in this case . You can check that by adding a simple OR condition in the if

    if(str.equalsIgnoreCase("d") || str.equalsIgnoreCase("e")){
        // last element "e" won't be removed as it is not accessed
        li.remove(str);   
    }

But if you remove any other element next() is called which throws the ConcurrentModificationException.

AllTooSir
  • 48,828
  • 16
  • 130
  • 164
2

ConcurrentException is raised because of the fail fast behaviour of the ArrayList. This means you can not modify the list while iterating it except Iterator#remove() .

Refer http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html

Jaydeep Rajput
  • 3,605
  • 17
  • 35
0

If you really want to iterate over an ArrayList and remove elements then you should do it this way:

for(int index = yourArrayList.size() - 1; index >= 0; index--) {
    if(yourCondition) {
        yourArrayList.remove(index);
    }
}
Marco Forberg
  • 2,634
  • 5
  • 22
  • 33
0

You can iterate "backwards" and remove elements, but not forward. So instead of iterating from the first element to the last, iterate from the last to the first.

Pseudo-code:

for(int i = list.size() -1; i >= 0; i--)
{
   list.remove(i);
}
Terje
  • 1,753
  • 10
  • 13
0

If you wish to remove all then.removeAll should do the trick rather than iterating through the collection. I think it is speedier too.

Amith
  • 6,818
  • 6
  • 34
  • 45