“Puns are the highest form of literature.”
― Alfred Hitchcock
Contents
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.