When List.remove() is called, the list shrinks, and the indices of all elements following the removed element are decremented by one.
If this operation is performed within a loop that iterates through the elements in ascending order, it will cause the loop to skip the element
immediately following the removed element.
There are three ways how to fix this issue:
Collection.removeIf(). This is the preferred solution. Collection.remove(). This approach is not
recommended, because it will raise an issue with rule {rule:java:S127} - "for" loop stop conditions should be invariant If the loop can be replaced with Java 8’s Collection.removeIf method, depending on the side effects of the loop and your Java target
version, then this is the preferred solution for this issue.
void removeFrom(List<String> list) {
// expected: iterate over all list elements
for (int i = 0; i < list.size(); i++) {
if (list.get(i).isEmpty()) {
list.remove(i); // Noncompliant, next element is skipped
}
}
}
void removeFrom(List<String> list) {
list.removeIf(String::isEmpty); // Compliant
}
If this is not possible due to side effects of the loop, replace the ascending loop with a descending loop. Descending loops are not affected by decrementing the element indices after the removed element, because they have already been iterated.
void removeFrom(List<String> list) {
// expected: iterate over all list elements
for (int i = 0; i < list.size(); i++) {
if (list.get(i).isEmpty()) {
list.remove(i); // Noncompliant, next element is skipped
}
}
}
void removeFrom(List<String> list) {
// expected: iterate over all list elements
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).isEmpty()) {
list.remove(i); // Compliant, elements after removed one have already been iterated
}
}
}
Another way to solve this issue is to adjust the loop counter after the call to Collection.remove to account for the index
decrement.
void removeFrom(List<String> list) {
// expected: iterate over all list elements
for (int i = 0; i < list.size(); i++) {
if (list.get(i).isEmpty()) {
list.remove(i); // Noncompliant, next element is skipped
}
}
}
This is not recommanded because it raises an issue with rule {rule:java:S127}.
void removeFrom(List<String> list) {
// expected: iterate over all list elements
for (int i = 0; i < list.size(); i++) {
if (list.get(i).isEmpty()) {
list.remove(i); // Compliant due to counter adjust in next line
i--; // Noncompliant with S127!
}
}
}