Week 154 — How can one safely remove elements while iterating over a collection?

Question of the Week #154
How can one safely remove elements while iterating over a collection?
6 Replies
MrMisha | Coder
Most collections don't allow modifying them while they are being iterated over using an Iterator/for-each loop. These collections would throw a ConcurrentModificationException when the next element is retrieved.
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
for (String s : someList) {//ConcurrentModificationException in the second iteration
System.out.println(s);
someList.remove(0);
}
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
for (String s : someList) {//ConcurrentModificationException in the second iteration
System.out.println(s);
someList.remove(0);
}
// equivalent code with Iterator
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
for (Iterator<String> it = someList.iterator(); it.hasNext();) {//ConcurrentModificationException in the second iteration
String s = it.next();
System.out.println(s);
someList.remove(0);
}
// equivalent code with Iterator
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
for (Iterator<String> it = someList.iterator(); it.hasNext();) {//ConcurrentModificationException in the second iteration
String s = it.next();
System.out.println(s);
someList.remove(0);
}
If removing the current element is necessary during iteration, the Iterator#remove method can be used:
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
for (Iterator<String> it = someList.iterator(); it.hasNext();) {
String s = it.next();
System.out.println(s);
it.remove();//removes the current element
}
System.out.println(someList);// []
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
for (Iterator<String> it = someList.iterator(); it.hasNext();) {
String s = it.next();
System.out.println(s);
it.remove();//removes the current element
}
System.out.println(someList);// []
However, there are cases where this is not applicable as e.g. elements other than the current one need to be removed. In cases like this, it may be possible to use an indexed loop and make sure to update the index as appropriate:
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
for (int i = 0; i < someList.size(); i++) {
String s = someList.get(i);
System.out.println(s);
if ("World".equals(s)) {
if (i > 0) {
// remove the element before
someList.remove(i - 1);
i--; // the index needs to be updated to prevent skipping elements
}
// remove the element afterwards
if (i < someList.size() - 1) {
someList.remove(i + 1);
// the index doesn't need to be updated when removing elements after the current one
}
}
}
System.out.println(someList);// [World]
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
for (int i = 0; i < someList.size(); i++) {
String s = someList.get(i);
System.out.println(s);
if ("World".equals(s)) {
if (i > 0) {
// remove the element before
someList.remove(i - 1);
i--; // the index needs to be updated to prevent skipping elements
}
// remove the element afterwards
if (i < someList.size() - 1) {
someList.remove(i + 1);
// the index doesn't need to be updated when removing elements after the current one
}
}
}
System.out.println(someList);// [World]
However, this is only possible with collections that support indexed iterations (typically only Lists) and correctly updating the index may be complicated.
MrMisha | Coder
In many cases, it is also possible to use methods provided by the collection API like removeIf:
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
someList.removeIf(element -> element.length() > 1 && Character.isUpperCase(element.codePointAt(0)));
System.out.println(someList);// [123]
List<String> someList = new ArrayList<>(List.of("Hello", "World", "123"));
someList.removeIf(element -> element.length() > 1 && Character.isUpperCase(element.codePointAt(0)));
System.out.println(someList);// [123]
Similarly, it is possible to just create an auxiliary collection containing the elements to remove and removing them after iteration:
List<String> someList = new ArrayList<>(List.of("Hello", "123", "456", "World"));

// remove all numbers except the first one
Set<String> toRemove = new HashSet<>();//contains elements to remove
boolean foundNumber = false;
for (String s : someList) {
if (s.matches("^\\d+$")) {//contains numbers and only numbers
if (foundNumber) {
toRemove.add(s);//mark as to be removed
}
foundNumber = true;
}
}
someList.removeAll(toRemove);
System.out.println(someList);//[Hello, 123, World]
List<String> someList = new ArrayList<>(List.of("Hello", "123", "456", "World"));

// remove all numbers except the first one
Set<String> toRemove = new HashSet<>();//contains elements to remove
boolean foundNumber = false;
for (String s : someList) {
if (s.matches("^\\d+$")) {//contains numbers and only numbers
if (foundNumber) {
toRemove.add(s);//mark as to be removed
}
foundNumber = true;
}
}
someList.removeAll(toRemove);
System.out.println(someList);//[Hello, 123, World]
📖 Sample answer from dan1st
MrMisha | Coder
In Java, to safely remove elements while iterating over a collection, use an Iterator with its remove() method or the removeIf method. Removing elements directly in a for-each loop can cause a ConcurrentModificationException.
MrMisha | Coder
that's my answer
⭐ Submission from mrmisha001
MrMisha | Coder
iterator. remove next item while hasNext
Submission from ziberlit
MrMisha | Coder
use Iterator.remove()
Submission from 4b_wang

Did you find this page helpful?