Removing items from a List within a loop


Collections

The collections framework in java.util provides a number of generic classes for sets of data with functionality that can't be provided by regular arrays.

Collections framework contains interfaces for Collection<O>, with main sub-interfaces List<O> and Set<O>, and mapping collection Map<K,V>. Collections are the root interface and are being implemented by many other collection frameworks. .


Removing items from a List within a loop

It is tricky to remove items from a list while within a loop, this is due to the fact that the index and length of the list gets changed.

Given the following list, here are some examples that will give an unexpected result and some that will give the correct result.

List<String> fruits = new ArrayList<String>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Strawberry");

INCORRECT

Removing in iteration of for statement Skips "Banana":

The code sample will only print Apple and Strawberry. Banana is skipped because it moves to index 0 once Apple is deleted, but at the same time i gets incremented to 1.

for (int i = 0; i < fruits.size(); i++) {
   System.out.println (fruits.get(i));
   if ("Apple".equals(fruits.get(i))) {
       fruits.remove(i);
   } 
}

Removing in the enhanced for statement Throws Exception:

Because of iterating over collection and modifying it at the same time.

Throws: java.util.ConcurrentModificationException

for (String fruit : fruits) {
   System.out.println(fruit);
   if ("Apple".equals(fruit)) {
       fruits.remove(fruit);
   }
}

CORRECT

Removing in while loop using an Iterator

Iterator<String> fruitIterator = fruits.iterator();
    while(fruitIterator.hasNext()) { 
        String fruit = fruitIterator.next(); 
        System.out.println(fruit);
        if ("Apple".equals(fruit)) {
            fruitIterator.remove();
    }
}

The Iterator interface has a remove() method built in just for this case. However, this method is marked as "optional" in the documentation, and it might throw an UnsupportedOperationException.

  • Throws: UnsupportedOperationException - if the remove operation is not supported by this iterator

Therefore, it is advisable to check the documentation to make sure this operation is supported (in practice, unless the collection is an immutable one obtained through a 3rd party library or the use of one of the Collections.unmodifiable...() method, the operation is almost always supported).

While using an Iterator a ConcurrentModificationException is thrown when the modCount of the List is changed from when the Iterator was created. This could have happened in the same thread or in a multi-threaded application sharing the same list.

A modCount is an int variable which counts the number of times this list has been structurally modified. A structural change essentially means an add() or remove() operation being invoked on Collection object (changes made by Iterator are not counted). When the Iterator is created, it stores this modCount and on every iteration of the List checks if the current modCount is same as and when the Iterator was created. If there is a change in the modCount value it throws a ConcurrentModificationException.

Hence for the above-declared list, an operation like below will not throw any exception:

Iterator<String> fruitIterator = fruits.iterator();
fruits.set(0, "Watermelon");
while(fruitIterator.hasNext()){
    System.out.println(fruitIterator.next());
}
 

But adding a new element to the List after initializing an Iterator will throw a ConcurrentModificationException:

Iterator<String> fruitIterator = fruits.iterator();
fruits.add("Watermelon");
while(fruitIterator.hasNext()){
   System.out.println(fruitIterator.next()); //ConcurrentModificationException here
}

Iterating backwards

for (int i = (fruits.size() - 1); i >=0; i--) {
    System.out.println (fruits.get(i));
    if ("Apple".equals(fruits.get(i))) {
        fruits.remove(i);
    }
}

This does not skip anything. The downside of this approach is that the output is reverse. However, in most cases where you remove items that will not matter. You should never do this with LinkedList.

Iterating forward, adjusting the loop index

for (int i = 0; i < fruits.size(); i++) {
    System.out.println (fruits.get(i));
    if ("Apple".equals(fruits.get(i))) {
        fruits.remove(i);
        i--;
    } 
}

This does not skip anything. When the ith element is removed from the List, the element originally positioned at index i+1 becomes the new ith element. Therefore, the loop can decrement i in order for the next iteration to process the next element, without skipping.

Using a "should-be-removed" list

ArrayList shouldBeRemoved = new ArrayList();
for (String str : currentArrayList) {
    if (condition) {
        shouldBeRemoved.add(str);
    }
}
currentArrayList.removeAll(shouldBeRemoved);

This solution enables the developer to check if the correct elements are removed in a cleaner way.

Filtering a Stream

A List can be streamed and filtered. A proper filter can be used to remove all undesired elements.

List<String> filteredList = fruits.stream().filter(p -> !"Apple".equals(p)).collect(Collectors.toList());
 

Note that unlike all the other examples here, this example produces a new List instance and keeps the original List unchanged.

Using removeIf

Saves the overhead of constructing a stream if all that is needed is to remove a set of items.

fruits.removeIf(p -> "Apple".equals(p));

Basic Programs