Removing Items in a Loop from a List

Learn about several methods to remove items in a loop from a java collection.

“Puns are the highest form of literature.”
― Alfred Hitchcock

1. Introduction

When you have a List or a Set or any other Collection, you may sometimes need to remove items in a loop. For example, you may be maintaining a list of messages for processing, and need to occasionally purge stale items from the list. In such a scenario, you would loop around the collection and remove the stale items.

In this scenario, when you use an enhanced for-each loop (as shown in the example below) and attempt to remove items, you would be faced with an exception: a ConcurrentModificationException.

List<String> alist = new ArrayList<>(Arrays.asList("apple",
                                                   "orange",
                                                   "banana",
                                                   "grapes",
                                                   "melon"));
for (String fruit : alist) {
    if ( fruit.equals("banana") ) alist.remove(fruit);
}
# throws
Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
        at java.util.ArrayList$Itr.next(ArrayList.java:851)
    ...

There are several ways to remove items in a loop and avoid this exception. We explain these methods in detail below.

2. Using Iterator

The Iterator class provides a method remove() which removes the current item safely without causing this exception.

System.out.println("t1: " + alist);
for (Iterator<String> it = alist.iterator() ; it.hasNext() ; ) {
    String fruit = it.next();
    if ( fruit.equals("banana") ) it.remove();
}
System.out.println("t2: " + alist);
# prints
t1: [apple, orange, banana, grapes, melon]
t2: [apple, orange, grapes, melon]

3. Removal Using Java 8 Lambda

When using Java 8, the List class (actually all Collection classes including Set, Queue, etc) provides the method removeIf() which allows removal of all items that match a condition.

System.out.println("t3: " + alist);
alist.removeIf(fruit -> fruit.length() == 5);
System.out.println("t4: " + alist);
# prints
t3: [apple, orange, banana, grapes, melon]
t4: [orange, banana, grapes]

4. Collect Items For Removal

An more traditional way is to collect the items to be removed in a separate Collection and use removeAll() to remove these items in a single operation.

System.out.println("t5: " + alist);
List<String> toBeRemoved = new ArrayList<>();
for (String fruit : alist) {
    if ( fruit.contains("o") ) toBeRemoved.add(fruit);
}
alist.removeAll(toBeRemoved);
System.out.println("t6: " + alist);
# prints
t5: [apple, orange, banana, grapes, melon]
t6: [apple, banana, grapes]

5. Using Java 8 Streams

As with most problems of these types involving collections, there are several solutions using java 8 streams.

The first one presented below uses java 8 streams instead of the loop above to collect the items to be removed.

System.out.println("t7: " + alist);
List<String> toBeRemoved = alist
    .stream()
    .filter(fruit -> fruit.contains("o"))
    .collect(Collectors.toList());
alist.removeAll(toBeRemoved);
System.out.println("t8: " + alist);

You can, of course, negate the condition in filter() and collect the items to be retained (rather than removed).

System.out.println("t9: " + alist);
List<String> toBeRetained = alist
    .stream()
    .filter(fruit -> ! fruit.contains("o"))
    .collect(Collectors.toList());
alist = toBeRetained;
System.out.println("t10: " + alist);

If you’d like to explicitly use negation, or you are obtaining the predicate function from elsewhere, you can use negate() as shown:

Predicate<String> f = fruit -> fruit.contains("o");
...
System.out.println("t9: " + alist);
List<String> toBeRetained = alist
    .stream()
    .filter(f.negate())
    .collect(Collectors.toList());
alist = toBeRetained;
System.out.println("t10: " + alist);

A small issue with assigning the list of items to be retained (toBeRetained) as shown is that the actual types of the target (alist) and the source may not match. For instance, Collectors.toList() may return non-mutable List, or in some other important way is different from the original. If that is important to you, use retainAll() instead.

List<String> toBeRetained = alist
    .stream()
    .filter(fruit -> !fruit.contains("o"))
    .collect(Collectors.toList());
alist.retainAll(toBeRetained);

6. Partitioning

Sometimes you may want to partition a collection into, say done and not done lists. You can do that using a predicate function as before with partitioningBy(). You end up with two separate lists partitioned according to your predicate function.

System.out.println("t11: " + alist);
Predicate<String> f = fruit -> fruit.contains("o");
Map<Boolean,List<String>> partitioned = alist
    .stream()
    .collect(Collectors.partitioningBy(fruit -> fruit.contains("o")));
System.out.println("t12: true => " + partitioned.get(true));
System.out.println("    false => " + partitioned.get(false));
# prints
t11: [apple, orange, banana, grapes, melon]
t12: true => [orange, melon]
    false => [apple, banana, grapes]

If you need to store the partitioned lists in a List of a specified type (for example, LinkedList), use the two-argument form of partitioningBy().

Map<Boolean,LinkedList<String>> partitioned = alist
    .stream()
    .collect(Collectors.partitioningBy(fruit -> fruit.contains("o"),
                                       Collectors.toCollection(LinkedList::new)));
System.out.println("t13: true => " + partitioned.get(true));
System.out.println("     type => " + partitioned.get(true).getClass().getName());
System.out.println("    false => " + partitioned.get(false));
System.out.println("     type => " + partitioned.get(false).getClass().getName());
# prints
t13: true => [orange, melon]
     type => java.util.LinkedList
    false => [apple, banana, grapes]
     type => java.util.LinkedList

Review

When removing items from a Collection in a loop, some care must be exercised to avoid a ConcurrentModificationException. Depending on your needs, there are several method to use including: using Iterator.remove(), specifying a predicate function with Collection.removeIf(), collecting items for removal in a separate list and some java 8 streams solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *