patternjavaCritical
Removing elements on a List while iterating through it
Viewed 0 times
whileiteratingelementsremovingthroughlist
Problem
I needed a way to remove elements on a
Supposedly something like this (illegal code):
But we all know that the above code is not allowed.
So what I'm doing right now is that I create another
I know it's not really long, but is there a better way to do this?
Is there some API that I can use?
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
Easy solution is to create a copy of the list and iterate through that.
Down-sides of this approach:
Java 8 Streams
Down-sides:
On the up-side, this is among the fastest for bigger lists.
If you're not using Java 8:
Java 8 method
Java 8 introduced the default method
Iterator.remove()
The only down-side of this approach is that you need to switch your for-each to a
Note: Instead of using a while-loop it can also be written as:
Conclusion
If you want to mutate the existing list,
See also
"When to use LinkedList over ArrayList?" on Stack Overflow
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.