Java list's .remove method works only for second last object inside for each loop
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.
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
}
}
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 List
and 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
.