Java 比较器用 a - b,遇到极值后排序错乱
两个 int 相减可能溢出并改变比较结果的符号。改用 Integer.compare,保留升降序与空值边界,并用极值核对比较关系。
Java 比较器写成 return a - b,普通小整数排序正常,混入很大的正数和负数后,结果却不再按数值递增。问题可能出在比较器自己:两个 int 相减仍按 32 位整数计算,差值溢出后,正负号会发生变化。
这里限定比较两个有符号 int 值的场景。下面的完整示例使用 Java 8 或更新版本;核心修复 Integer.compare 从 Java 7 开始可用。
先检查比较结果,不必猜排序算法
升序比较器要求:左值小于右值时返回负数,相等时返回零,大于时返回正数。它不要求返回实际差值。
例如 Integer.MAX_VALUE 明明大于 -1,但表达式 Integer.MAX_VALUE - (-1) 的数学结果是 2147483648,超出 int 上限,在 Java 的整数运算中得到 Integer.MIN_VALUE。比较器因此把更大的值判成更小。
普通整数减法不会因为这种溢出自动抛出异常。只用 1、2、3 测试,容易漏掉问题;排序输出偶尔正确,也不能证明比较器满足一致性要求。
检查实际的比较函数及字段类型。如果它已经使用正确的大小比较,就应继续查排序键是否在排序过程中被修改、是否处理了空值,以及多字段排序规则,不能把所有乱序归为减法溢出。
改用 Integer.compare,保留原数组作对照
import java.util.Arrays;
public class SafeIntSort {
public static void main(String[] args) {
Integer[] original = {
Integer.MAX_VALUE, -1, 0, Integer.MIN_VALUE, 1
};
Integer[] sorted = original.clone();
Arrays.sort(sorted, (a, b) -> Integer.compare(a, b));
System.out.println(Arrays.toString(sorted));
}
}
这组输入的预期升序为 [-2147483648, -1, 0, 1, 2147483647]。示例复制了数组,方便比对修复前后的顺序;数组元素均非 null,否则拆箱本身仍可能抛出 NullPointerException。
对象按某个 int 字段排序时,将比较器主体改成 Integer.compare(a.getValue(), b.getValue())。降序可以交换这两个参数。字段若实际为 long,应使用 Long.compare,不要先强制缩窄为 int。
不要用 Integer.signum(a - b) 补救:传给 signum 的差值已经可能溢出。写成 (int) ((long) a - b) 虽然扩大了中间运算范围,最后转回 int 又可能截断,因此也不适合作为比较器返回值。
验收要覆盖边界与比较关系
在开发环境用最大值、最小值、零、正负一和重复值检查排序,并将同一批值以不同初始顺序传入。相同元素应比较为零;交换两个不同参数后,比较结果的符号应相反;三元素的大小关系应满足传递性。
如果比较器原先还有第二排序键,修复第一字段后仍需保留并检查第二字段规则。不要为了消除乱序,把所有异常或相同主键一律处理为比较相等。
比较器改动不会修改对象字段,却可能改变导出顺序、分页结果或界面排列。发布前保留原始输入和排序约定;已经写出的错误顺序需从原数据重新生成,恢复旧比较器不能修复这些结果。


