127 lines
3.9 KiB
Plaintext
127 lines
3.9 KiB
Plaintext
== Why is this an issue?
|
|
|
|
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.
|
|
|
|
== How to fix it
|
|
|
|
There are three ways how to fix this issue:
|
|
|
|
1. Replace the loop with a call to `Collection.removeIf()`. This is the preferred solution.
|
|
2. Replace the ascending loop with a descending loop. Use this approach if the preferred solution is not possible due to side effects of the loop.
|
|
3. Adjust the loop counter within the loop body after the call to `Collection.remove()`. **This approach is not recommended**, because it will raise an issue with rule _S127 - "for" loop stop conditions should be invariant_
|
|
|
|
=== Code examples
|
|
|
|
==== Noncompliant code example
|
|
|
|
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.
|
|
|
|
[source,java,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
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
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,java,diff-id=1,diff-type=compliant]
|
|
----
|
|
void removeFrom(List<String> list) {
|
|
list.removeIf(String::isEmpty); // Compliant
|
|
}
|
|
----
|
|
|
|
==== Noncompliant code example
|
|
|
|
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.
|
|
|
|
[source,java,diff-id=2,diff-type=noncompliant]
|
|
----
|
|
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
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,java,diff-id=2,diff-type=compliant]
|
|
----
|
|
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
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
==== Noncompliant code example
|
|
|
|
Another way to solve this issue is to adjust the loop counter after the call to `Collection.remove` to account for the index decrement.
|
|
|
|
[source,java,diff-id=3,diff-type=noncompliant]
|
|
----
|
|
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
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
**This is not recommanded** because it raises an issue with rule S127.
|
|
|
|
[source,java,diff-id=3,diff-type=compliant]
|
|
----
|
|
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!
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
== Resources
|
|
|
|
=== Documentation
|
|
|
|
* https://docs.oracle.com/javase/7/docs/api/java/util/Collection.html#remove(java.lang.Object)[Java SE 7 API Specification: Collection.remove]
|
|
* https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#removeIf-java.util.function.Predicate-[Java SE 8 API Specification: Collection.removeIf]
|
|
* https://sonarsource.github.io/rspec/#/rspec/S6068/java[S127 - "for" loop stop conditions should be invariant]
|
|
|
|
ifdef::env-github,rspecator-view[]
|
|
|
|
'''
|
|
== Implementation Specification
|
|
(visible only on this page)
|
|
|
|
=== Message
|
|
|
|
Verify that "remove()" is used correctly
|
|
|
|
|
|
endif::env-github,rspecator-view[]
|