按结束时间升序、结束时间相同时再按开始时间排序,本来只是两个排序键。遇到 null 后添加 nullsLast,却又报方法引用错误,问题可能不在静态方法,而在比较器的类型。
两次错误对应两个问题
最初直接按字段排序:
Comparator.comparing(TaskDto::getEndTime)
.thenComparing(TaskDto::getStartTime);
时间字段有 null 时,默认自然顺序比较可能出现空指针异常。随后若照搬字符串示例:
Comparator.comparing(
TaskDto::getEndTime,
Comparator.nullsLast(String::compareTo)
);
而 getEndTime 实际返回 Date,比较器就无法匹配。方法引用相关编译诊断不等于必须把 getter 改成 static;应先核对提取键的返回类型。
使用 Date 比较器的完整示例
import java.util.*;
public class DateSortDemo {
static final class TaskDto {
private final String id;
private final Date endTime;
private final Date startTime;
TaskDto(String id, Date endTime, Date startTime) {
this.id = id;
this.endTime = endTime;
this.startTime = startTime;
}
Date getEndTime() { return endTime; }
Date getStartTime() { return startTime; }
}
public static void main(String[] args) {
List<TaskDto> tasks = new ArrayList<>(Arrays.asList(
new TaskDto("A", null, new Date(1)),
new TaskDto("B", new Date(2), null),
new TaskDto("C", null, null)
));
Comparator<TaskDto> comparator = Comparator
.comparing(TaskDto::getEndTime,
Comparator.nullsLast(Date::compareTo))
.thenComparing(TaskDto::getStartTime,
Comparator.nullsLast(Date::compareTo));
tasks.sort(comparator);
tasks.forEach(task -> System.out.println(task.id));
// 按上述规则,顺序为 B、A、C
}
}
这里的 Date 数值只用于构造排序样本,不表达业务时区。Stream 的 sorted 也能接收同一个 comparator;List.sort 会修改原列表,Stream.sorted 则形成排序后的流。
nullsLast 只包装它接收到的值
传给 comparing 的第二个参数比较的是提取出的字段,不是 TaskDto 本身。如果列表允许出现整个对象为 null,要再在外层使用 Comparator.nullsLast(comparator),或者在输入边界拒绝空对象。Java 8 Comparator
thenComparing 只在前一个比较结果相等时生效,不是“只要结束时间为空就随意用开始时间替代结束时间”。若两个结束时间都为 null,它们在第一关键字上相等,再比较开始时间。
空值排在后面是业务规则
结束时间为空可以表示尚未完成、未知或缺失,各自的排序要求可能不同。还应考虑所有键都相等时的展示顺序,必要时再加稳定的业务标识作为第三关键字,而不是为消除异常就默认采用某个业务含义。