comparator.compare()必须返回负数、0、正数表示小于、等于、大于,不可硬编码±1;应复用compareto()或double.compare();多级排序用thencomparing()链式组合,并为每层显式处理null。

Comparator.compare() 返回值必须严格遵守三值约定
返回负数、0、正数分别代表“小于”“等于”“大于”,不能只用 1 和 -1 硬编码。Java 集合框架(如 Collections.sort()、TreeSet)依赖符号判断顺序,若始终返回 1 或 -1,会导致排序逻辑错乱甚至死循环。
正确做法是直接复用已有比较结果,或用减法(仅限整型且无溢出风险):
Comparator<person> byAge = (a, b) -> a.getAge() - b.getAge(); // 安全仅当 age 是非负 int 且差值不超 int 范围</person>
更通用的写法是:
Comparator<person> byName = (a, b) -> a.getName().compareTo(b.getName());</person>
- 字符串、包装类、时间类等实现了
Comparable的类型,优先调用其compareTo() - 避免手动写
a > b ? 1 : a —— 冗余且易错 - 对浮点数用
Double.compare(a, b),别用==或减法
链式组合多个 Comparator:thenComparing() 是核心技巧
一个 Comparator 只能表达单一主序;真实业务常需“先按部门升序,部门相同时按薪资降序”。这时不要手写嵌套 if,而应用 thenComparing() 组合:
Comparator<employee> comp = Comparator.comparing(Employee::getDept)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparing(Employee::getId);</employee>
-
thenComparing()后续规则仅在前序结果为0(即相等)时生效 - 第二个参数可传入任意
Comparator,包括Comparator.reverseOrder()或自定义匿名实现 - 注意:所有参与链式调用的字段都应为非 null,否则触发
NullPointerException;必要时用thenComparing(Comparator.nullsLast(...))
处理 null 值必须显式声明策略
默认情况下,任何含 null 的比较都会抛 NullPointerException。Java 提供了 Comparator.nullsFirst() 和 Comparator.nullsLast() 包装器,但它们只作用于单层比较,不能自动透传到链式后续步骤中。
错误写法(只保护了第一层):
Comparator<string> c = Comparator.nullsLast(String::compareTo).thenComparing(String::length); // null 元素在第二层仍会 NPE</string>
正确写法(每层独立处理):
Comparator<employee> safeComp = Comparator.comparing(
Employee::getDept, Comparator.nullsLast(String::compareTo))
.thenComparing(Employee::getSalary, Comparator.nullsFirst(Comparator.reverseOrder()));</employee>
-
nullsFirst/nullsLast接收一个子Comparator,它负责比较两个非 null 值 - 如果字段本身可能为 null,且你希望它排在前面,就用
nullsFirst(Comparator.naturalOrder()) - 不要假设
Comparator.nullsLast(Comparator.naturalOrder())能覆盖整个链 —— 它只管自己这一级
Lambda 表达式 vs 方法引用:性能与可读性的取舍
方法引用(如 String::length)在语义清晰、逻辑简单时更推荐;但一旦涉及条件判断、转换或异常处理,就必须用 lambda。
例如,按文件扩展名排序:
// ✅ 方法引用不够用,必须用 lambda 提取扩展名
Comparator<file> byExt = (f1, f2) -> {
String ext1 = getFileExtension(f1);
String ext2 = getFileExtension(f2);
return ext1.compareTo(ext2);
};</file>
- 方法引用本质是编译器生成的函数式接口实例,无法包含分支或 try-catch
- lambda 中若做耗时操作(如数据库查询、IO),要考虑是否该提前预计算并缓存结果
- 复杂逻辑建议提取为独立的静态方法,再用方法引用调用,兼顾可测性与复用性
真正麻烦的不是写多个 Comparator,而是忘记它们之间的 null 策略一致性,以及误以为 thenComparing() 会自动继承上层的 null 处理逻辑。










