This rule raises an issue when compareTo() or compare() orders values by subtracting integers. Integer subtraction can
overflow and reverse the comparison result.
Comparable.compareTo() and Comparator.compare() must return a negative integer, zero, or a positive integer as the first
value is less than, equal to, or greater than the second. The magnitude of that result does not matter, only its sign.
A common shortcut is to return the difference of two numeric fields:
return this.value - other.value;
Java integer operators wrap on overflow instead of throwing. When the true difference is larger than Integer.MAX_VALUE or smaller than
Integer.MIN_VALUE, the wrapped result can have the wrong sign. For example, (int) (1_000_000_000L - (-1_500_000_000L)) is
negative even though the first value is greater.
The same problem appears when a long difference is narrowed to int:
return (int) (this.timestamp - other.timestamp);
That broken ordering violates the comparison contract. Sorting with Arrays.sort() or Collections.sort() can throw
IllegalArgumentException: Comparison method violates its general contract!. Ordered collections such as TreeSet and
TreeMap can become corrupted.
Use Integer.compare() for int values and Long.compare() for long values. Those methods compare
without computing a difference that can overflow.
This rule does not flag floating-point subtraction in ordering methods. See {rule:java:S9148}.
This rule also does not flag subtraction of two values with a provably bounded range, such as array lengths, String.length(),
Collection.size(), Enum.ordinal(), or a value masked with a non-negative bitwise AND constant. Their difference cannot
overflow, so the subtraction is safe.
Replace the subtraction with Integer.compare() or Long.compare(), matching the operand type.
class TimestampedEvent implements Comparable<TimestampedEvent> {
private long timestamp;
@Override
public int compareTo(TimestampedEvent other) {
return (int) (this.timestamp - other.timestamp); // Noncompliant
}
}
class TimestampedEvent implements Comparable<TimestampedEvent> {
private long timestamp;
@Override
public int compareTo(TimestampedEvent other) {
return Long.compare(this.timestamp, other.timestamp);
}
}
import java.util.Comparator;
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person left, Person right) {
return left.age - right.age; // Noncompliant
}
}
import java.util.Comparator;
class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person left, Person right) {
return Integer.compare(left.age, right.age);
}
}
import java.util.List;
void sortById(List<Event> events) {
events.sort((left, right) -> (int) (left.id - right.id)); // Noncompliant
}
import java.util.List;
void sortById(List<Event> events) {
events.sort((left, right) -> Long.compare(left.id, right.id));
}