It is tricky to remove items from a list while within a loop, this is due to the fact that the index and length of the list gets changed.
Given the following list, here are some examples that will give an unexpected result and some that will give the correct result.
List<String> fruits = new ArrayList<String>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Strawberry");
for
statement Skips "Banana":The code sample will only print Apple
and Strawberry
. Banana
is skipped because it moves to index 0
once Apple
is deleted, but at the same time i
gets incremented to 1
.
for (int i = 0; i < fruits.size(); i++) {
System.out.println (fruits.get(i));
if ("Apple".equals(fruits.get(i))) {
fruits.remove(i);
}
}
for
statement Throws Exception:Because of iterating over collection and modifying it at the same time.
Throws: java.util.ConcurrentModificationException
for (String fruit : fruits) {
System.out.println(fruit);
if ("Apple".equals(fruit)) {
fruits.remove(fruit);
}
}
Iterator
Iterator<String> fruitIterator = fruits.iterator();
while(fruitIterator.hasNext()) {
String fruit = fruitIterator.next();
System.out.println(fruit);
if ("Apple".equals(fruit)) {
fruitIterator.remove();
}
}
The Iterator
interface has a remove()
method built in just for this case. However, this method is marked as "optional" in the documentation, and it might throw an UnsupportedOperationException
.
Throws: UnsupportedOperationException - if the remove operation is not supported by this iterator
Therefore, it is advisable to check the documentation to make sure this operation is supported (in practice, unless the collection is an immutable one obtained through a 3rd party library or the use of one of the Collections.unmodifiable...()
method, the operation is almost always supported).
While using an Iterator
a ConcurrentModificationException
is thrown when the modCount
of the List
is changed from when the Iterator
was created. This could have happened in the same thread or in a multi-threaded application sharing the same list.
A modCount
is an int
variable which counts the number of times this list has been structurally modified. A structural change essentially means an add()
or remove()
operation being invoked on Collection
object (changes made by Iterator
are not counted). When the Iterator
is created, it stores this modCount
and on every iteration of the List
checks if the current modCount
is same as and when the Iterator
was created. If there is a change in the modCount
value it throws a ConcurrentModificationException
.
Hence for the above-declared list, an operation like below will not throw any exception:
Iterator<String> fruitIterator = fruits.iterator();
fruits.set(0, "Watermelon");
while(fruitIterator.hasNext()){
System.out.println(fruitIterator.next());
}
But adding a new element to the List
after initializing an Iterator
will throw a ConcurrentModificationException
:
Iterator<String> fruitIterator = fruits.iterator();
fruits.add("Watermelon");
while(fruitIterator.hasNext()){
System.out.println(fruitIterator.next()); //ConcurrentModificationException here
}
for (int i = (fruits.size() - 1); i >=0; i--) {
System.out.println (fruits.get(i));
if ("Apple".equals(fruits.get(i))) {
fruits.remove(i);
}
}
This does not skip anything. The downside of this approach is that the output is reverse. However, in most cases where you remove items that will not matter. You should never do this with LinkedList
.
for (int i = 0; i < fruits.size(); i++) {
System.out.println (fruits.get(i));
if ("Apple".equals(fruits.get(i))) {
fruits.remove(i);
i--;
}
}
This does not skip anything. When the i
th element is removed from the List
, the element originally positioned at index i+1
becomes the new i
th element. Therefore, the loop can decrement i
in order for the next iteration to process the next element, without skipping.
ArrayList shouldBeRemoved = new ArrayList();
for (String str : currentArrayList) {
if (condition) {
shouldBeRemoved.add(str);
}
}
currentArrayList.removeAll(shouldBeRemoved);
This solution enables the developer to check if the correct elements are removed in a cleaner way.
In Java 8 the following alternatives are possible. These are cleaner and more straight forward if the removing does not have to happen in a loop.
A List
can be streamed and filtered. A proper filter can be used to remove all undesired elements.
List<String> filteredList =
fruits.stream().filter(p -> !"Apple".equals(p)).collect(Collectors.toList());
Note that unlike all the other examples here, this example produces a new List
instance and keeps the original List
unchanged.
removeIf
Saves the overhead of constructing a stream if all that is needed is to remove a set of items.
fruits.removeIf(p -> "Apple".equals(p));