arrays.sort传comparator时遇null崩溃,关键在提前拦截而非修改比较逻辑;推荐用nullsfirst/nullslast包装,或自定义compare分支处理null含义,避免直接调用null方法。

Arrays.sort 传 Comparator 时遇到 null,不是 Comparator 本身出错,而是比较逻辑里对 null 调用了方法(比如 s1.compareTo(s2) 或 obj.getField().compareTo(...))。关键不在“能不能写”,而在“怎么写才不崩”——核心是提前拦截 null,不让它进实际比较。
用 nullsFirst / nullsLast 包装已有比较器
这是最常用、最安全的写法。它不修改原始比较逻辑,只在最外层统一处理 null 的位置。
- nullsFirst(Comparator):所有 null 排最前,非 null 按你给的比较器排
- nullsLast(Comparator):所有 null 排最后,非 null 照常比较
- 注意:括号里传的比较器(如
String::compareTo)仍要求输入非 null —— 它只负责比“有值”的部分
示例:按用户姓名排序,null 用户排最前
Arrays.sort(users, Comparator.nullsFirst(Comparator.comparing(User::getName, String::compareTo)));
字段提取可能为 null?必须包装取值过程
如果 User::getName 返回 null,直接传 String::compareTo 会崩。正确做法是把「提取 + 比较」两步都包进 null 安全逻辑里。
- 推荐写法:
Comparator.comparing(User::getName, Comparator.nullsFirst(String::compareTo)) - 等价于:先取 name,若为 null 则由外层
nullsFirst拦住;若非 null,再交给String::compareTo - 错误写法:
Comparator.comparing(User::getName, String::compareTo)—— name 是 null 时直接进String::compareTo,NPE
需要自定义 null 含义?手写 compare 分支处理
当 null 不只是“排最前/后”,而是要当作某个默认值(比如 null 视为 0、或视为空字符串),就得自己控制每个分支。
- 先判断 u1 和 u2 是否为 null,穷举四种组合(都 null、仅 u1 null、仅 u2 null、都不 null)
- 避免任何对 null 的方法调用,比如
u1.getName().compareTo(...)必须加判空 - 示例:年龄字段为 null 时,视作 -1(最小值)
Arrays.sort(users, (u1, u2) -> {
Integer a = u1 != null ? u1.getAge() : -1;
Integer b = u2 != null ? u2.getAge() : -1;
return Integer.compare(a, b);
});基本类型包装类也要防溢出和 null
用 lambda 写 (a, b) -> a - b 看似简洁,但整数相减可能溢出(如 Integer.MAX_VALUE - (-1) 得负数),导致排序错乱;同时若 a 或 b 是 null,也会 NPE。
- 替代方案:用
Integer.compare(a, b),它内部做了溢出防护且接受 null(但注意:该方法本身不处理 null,需配合 nullsFirst 等) - 安全组合:
Comparator.nullsLast(Integer::compare)或Comparator.nullsFirst(Comparator.naturalOrder()) - 字符串忽略大小写:
String::compareToIgnoreCase,别手写s1.toLowerCase().compareTo(s2.toLowerCase())(效率低且 s1/s2 为 null 时崩)











