HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaCritical

Removing elements on a List while iterating through it

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
whileiteratingelementsremovingthroughlist

Problem

I needed a way to remove elements on a List while iterating through it.

Supposedly something like this (illegal code):

List nums = new ArrayList();
nums.add(1);
nums.add(2);
nums.add(3);
...
nums.add(10);

System.out.println("BEFORE REMOVE: " + nums);

for (Integer integer : nums) {
    if (integer < 3) {
        //not allowed
        nums.remove(integer);
    }
}


But we all know that the above code is not allowed.

So what I'm doing right now is that I create another List, where I'm putting every element that meets a given condition, then iterate through that List to get the elements that I need to remove on the first List.

List toRemove = new ArrayList();
for (Integer integer : nums) {
    if (integer < 3) {
        toRemove.add(integer);
    }
}

for (Integer integer : toRemove) {
    nums.remove(integer);
}

System.out.println("AFTER REMOVE: " + nums);


I know it's not really long, but is there a better way to do this?

Is there some API that I can use?

Solution

There are several ways to do this. Let's look at the alternatives:

Iterating over a copy, removing from original

This is a simple solution for the underlying problem of your first code: A ConcurrentModificationException is thrown because you iterate through the list and removing from it at the same time.

Easy solution is to create a copy of the list and iterate through that.

for (Integer integer : new ArrayList<>(nums)) {
    if (integer < 3) {
        nums.remove(integer);
    }
}


Down-sides of this approach:

  • Creates a copy of the original list, which requires memory and an operation which performance depends on the type of the list (ArrayList, LinkedList, etc.)



  • Additionally, nums.remove(value) is a \$O(n)\$ operation. Making this loop overall \$O(n^2)\$



Java 8 Streams

List filteredList = nums.stream().filter(i -> i >= 3).collect(Collectors.toList());


Down-sides:

  • Does not actually modify the existing list, so if references to the list are spread around various variables, you still have some old elements that just shouldn't be in that list.



  • Creates various stream-related objects which might not be the most effective option.



On the up-side, this is among the fastest for bigger lists.

If you're not using Java 8:

List originalList;
List newList = new YourFavoriteListType<>();
for (Object obj : originalList) {
    if (shouldKeep(obj)) {
        newList.add(obj);
    }
}


Java 8 method

nums.removeIf(i -> i < 3);


Java 8 introduced the default method removeIf on the Collection interface. This allows different implementations to have implementation-specific performance-optimized implementations of this method.

Iterator.remove()

Iterator it = nums.iterator();
while (it.hasNext()) {
    Integer integer = it.next();
    if (integer < 3) {
        it.remove();
    }
}


The only down-side of this approach is that you need to switch your for-each to a while. However, this approach is the most efficient one, especially for LinkedList where it is \$O(n)\$ (it's \$O(n^2)\$ for ArrayList because it has to copy array data on each remove(index) call). This is the approach I would recommend in most cases.

Note: Instead of using a while-loop it can also be written as:

for (Iterator it = list.iterator(); it.hasNext(); ) {
    Integer integer = it.next();
    ...


Conclusion

If you want to mutate the existing list, removeIf is the solution I would go with. If you like functional programming and prefer a new list instead of mutating the existing one, then go with the list.stream().filter(...).collect(Collectors.toList()) approach.

See also

"When to use LinkedList over ArrayList?" on Stack Overflow

Code Snippets

for (Integer integer : new ArrayList<>(nums)) {
    if (integer < 3) {
        nums.remove(integer);
    }
}
List<Integer> filteredList = nums.stream().filter(i -> i >= 3).collect(Collectors.toList());
List<Object> originalList;
List<Object> newList = new YourFavoriteListType<>();
for (Object obj : originalList) {
    if (shouldKeep(obj)) {
        newList.add(obj);
    }
}
nums.removeIf(i -> i < 3);
Iterator<Integer> it = nums.iterator();
while (it.hasNext()) {
    Integer integer = it.next();
    if (integer < 3) {
        it.remove();
    }
}

Context

StackExchange Code Review Q#64011, answer score: 80

Revisions (0)

No revisions yet.