17

Is this a valid way to find and remove item from a LinkedList in Java using a for each loop, is it possible that inconsistency may arise:

for(ObjectType ob : obList) {
  if(ob.getId() == id) {
    obList.remove(ob);
    break;
   }
}

9 Answers 9

19

Others have mentioned the valid point that normally this is not how you remove an object from a collection. HOWEVER, in this case it's fine since you break out of the loop once you remove.

If you want to keep iterating after a remove, though, you need to use an iterator. Otherwise you'll get a ConcurrentModificationException, or in the more general case, undefined behavior.

So yes, if you break out of the foreach after you remove, you'll be fine.


To those who's saying that this will fail because you can't modify a collection in a foreach -- this is true only if you want to keep iterating. That's not the case here, so this shortcut is fine.

A ConcurrentModificationException is checked and thrown by the iterator. Here, after the remove (which qualifies as concurrent modification), you break out of the loop. The iterator doesn't even get a chance to detect it.

It may be best if you add a comment on the break, why it's absolutely necessary, etc, because if this code is later modified to continue iterating after a remove, it will fail.

I would treat this idiom similar to goto (or rather, labeled break/continue): it may seem wrong at first, but when used wisely, it makes for a cleaner code.

Sign up to request clarification or add additional context in comments.

4 Comments

I haven't checked the language specification, but isn't the foreach syntax just compiler magic that uses an iterator behind the scenes?
Yes, it does use an iterator behind the scene, and that iterator is hidden from you.
But it's very likely to fail in future version, when the nexte developer tries to add some functionality without "seeing" that pitfall. You should document it clearly then.
If this is a linked list then Iterator.remove() is far more efficient than List.remove(Object), since the latter has to search for the object all over again. I would use the Iterator and it's remove on principle.
7

It is best to use an iterator and use it's remove method when searching for an object by iterating over a collection in order to remove it. This is because

  1. The collection could be, for example, a linked list (and in your case it is) whose remove method means searching for the object all over again, which search could have O(n) complexity.
  2. You can't continue iteration after the remove unless you use the iterator's remove method. Right now you are removing the first occurrence - in future you might need to remove all matching occurrences, in which case you then have to rewrite the loop.

I recommend, on principle, foregoing the enhanced for and using something like this instead:

for(Iterator<ObjectType> it=obList.iterator(); it.hasNext(); ) {
    if(it.next().getId()==id) { 
        it.remove(); 
        break;
        }
    } 

That way you are not making assumptions about the underlying list that could change in the future.


Compare the code to remove the last entry called by the iterator remove (formatting Sun's):

private E remove(Entry<E> e) {
    if (e == header)
        throw new NoSuchElementException();

    E result = e.element;
    e.previous.next = e.next;
    e.next.previous = e.previous;
    e.next = e.previous = null;
    e.element = null;
    size--;
    modCount++;
    return result;
}

against what remove(Object) must do:

public boolean remove(Object o) {
    if (o==null) {
        for (Entry<E> e = header.next; e != header; e = e.next) {
            if (e.element==null) {
                remove(e);
                return true;
            }
        }
    } else {
        for (Entry<E> e = header.next; e != header; e = e.next) {
            if (o.equals(e.element)) {
                remove(e);
                return true;
            }
        }
    }
    return false;
}

1 Comment

You didn't answered what I asked but it's nice insight.
6

You should use iterator.remove():

Removes from the underlying collection the last element returned by the iterator (optional operation). This method can be called only once per call to next. 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 this method.

1 Comment

"while the iteration is in progress" -- this is the important distinction here. In his case, once he modifies the collection, he aborts the iteration. That's why we're having this discussion. For the general case, though, you are absolutely correct.
4

Edit: Indeed, it will not fail thanks to the break. See polygenelubricant's answer for details.

However, this is dangerous way to do. To concurrently iterate and modify a collection in Java, you must use the "ListIterator" object, and use the iterator's own "add()" and "remove()" methods, and not use the ones on the collection.

You can check the java doc for the "java.util.Iterator" and "java.util.ListIterator" classes

4 Comments

"Iterator" does not have an "add()" method. Only ListIterator has. You should point that out.
@Zorglub, pay attention to the break. This code will not fail.
It might not "fail", but the code is, in any normal sense, wrong.
@Tom this is what I felt myself!
1

Try something like this:

Iterator<ObjectType> iter = obList.iterator();
while (iter.hasNext()) {
  ObjectType ob = iter.next();
  if(ob.getId() == id) {
    iter.remove();
    break;
  }
}

That's one of the last places where an Iterator cannot be replaced by a foreach loop.

5 Comments

It's not necessary to be this verbose if you break after a remove.
@polygenelubricants : oh, you're right. I wasn't realizing that the Exception would only occur in the next loop.
If this is a linked list then Iterator.remove() is far more efficient than List.remove(Object), since the latter has to search for the object all over again. I would use the Iterator and it's remove on principle.
@Software Monkey, so it means the code I presented in the question can be written as obList.remove(ob)
@Xolve: Provided (a) you collection is either small or you can tolerate the extra search for the object you just found, and (b) you break after removing and don't continue iteration. See my answer for more detail.
1

To avoid a ConcurrentModifiationException, you could do:

final Iterator<ObjectType> i = obList.iterator();
while (i.hasNext()) {
    if (i.next().getId() == id) {
        i.remove();
    }
}

or

for (int i = 0; i < obList.size(); i++) {
    if (obList[i].getId() == id) {
        obList.remove(i);
    }
}

I would prefer the first. Handling indices is more errorprone and the iterator may be implemented efficiently. And the first suggestion works with Iterable while the second requires a List.

4 Comments

Just use the iterator in a for loop.
That would leave the last part of the loop empty. I dont like that. A while fits perfectly there.
This code can potentially do multiple removals. It's a different behavior than the original code, which removes at most one element.
@polygenelubricants You are right, but getId() implies that there is only one element per id.
1

in java8 you can just use Collection#removeIf

like this:

List<String> myList = new ArrayList<String>();

myList.removeIf(tex-> Objects.isNull(tex));   // myList.removeIf(Objects::isNull);

Comments

0

The above second loop should be changed a bit

for (int i = 0; i < obList.size(); ) {
    if (obList.get(i).getId() == id) {
        obList.remove(i);
        continue
    }
    ++i;
}

or

for (int i = obList.size() - 1; i >= 0; --i) {
    if (obList.get(i).getId() == id) {
        obList.remove(i);
    }
}

Comments

0

A CopyOnWriteArrayList might be what you're looking for. When mutative operations are performed, a copy of the underlying array is made. This allows modification of list elements while inside a for-each loop. Remember though that this is not a linked list and can be quite inefficient.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class Main {

    public static void main(String[] args) {
        List<String> myList = new CopyOnWriteArrayList<String>();

        myList.add("a");
        myList.add("b");
        myList.add("c");

        // Will print [a, b, c]
        System.out.println(myList);

        for (String element : myList) {
            if (element.equals("a")) {
                myList.remove(element);
            }
        }

        // Will print [b, c]
        System.out.println(myList);
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.