Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java list's .remove method works only for second last object inside for each loop [duplicate]

Tags:

java

foreach

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.

like image 661
amit bakle Avatar asked Apr 18 '13 10:04

amit bakle


People also ask

Can we remove an element by using forEach loop?

It is not recommended adding or removing elements from a list within a loop as an index of its elements, and the length of the list is changed. This might lead to the incorrect output, or java.

Can I remove element from list while iterating Java?

In Java 8, we can use the Collection#removeIf API to remove items from a List while iterating it.

How will you efficiently remove elements while iterating a collection?

Using ListIterator If you are working with lists, another technique consists in using a ListIterator which has support for removal and addition of items during the iteration itself.

How do you remove one Object from a list in Java?

The remove(Object obj) method of List interface in Java is used to remove the first occurrence of the specified element obj from this List if it is present in the List. Parameters: It accepts a single parameter obj of List type which represents the element to be removed from the given List.


4 Answers

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.

like image 183
prasanth Avatar answered Oct 26 '22 10:10

prasanth


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
    }
}
like image 45
anubhava Avatar answered Oct 26 '22 10:10

anubhava


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

like image 22
Jaydeep Rajput Avatar answered Oct 26 '22 10:10

Jaydeep Rajput


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.

like image 21
AllTooSir Avatar answered Oct 26 '22 11:10

AllTooSir