0Pricing
Java Academy · 课时

使用 thenComparing 进行多关键字排序

使用 thenComparing 链接比较器,按优先级依次根据多个字段排序

使用 thenComparing 进行多关键字排序 是 CoddyKit 上的免费 Java Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

多键排序

thenComparing() 会链接多个比较器,以便根据多个字段排序。先进行主要排序;如果相等,再进行次要排序;依此类推。

基本的 thenComparing

使用 thenComparing() 在主要比较器结果相等时进行进一步比较。

import java.util.*;

record Student(String name, int grade, double gpa) {}

List<Student> students = List.of(
    new Student("Alice",   10, 3.9),
    new Student("Bob",     11, 3.7),
    new Student("Charlie", 10, 3.8),
    new Student("Diana",   11, 3.7)
);

Comparator<Student> sort = Comparator.comparingInt(Student::grade)
    .thenComparingDouble(Student::gpa).reversed()
    .thenComparing(Student::name);

students.stream().sorted(sort).forEach(System.out::println);

三级排序

根据需要链接任意多个 thenComparing 层级。

record Employee(String dept, String team, String name, double salary) {}

List<Employee> employees = List.of(
    new Employee("Eng", "Backend", "Alice",   95000),
    new Employee("Eng", "Backend", "Bob",     92000),
    new Employee("Eng", "Frontend","Charlie", 88000),
    new Employee("Mkt", "Growth",  "Diana",   75000)
);

Comparator<Employee> sort = Comparator
    .comparing(Employee::dept)
    .thenComparing(Employee::team)
    .thenComparing(Comparator.comparingDouble(Employee::salary).reversed());

employees.stream().sorted(sort)
    .forEach(e -> System.out.printf("%s > %s > %s: $%.0f%n",
        e.dept(), e.team(), e.name(), e.salary()));

使用 Comparator 调用 thenComparing

当次要键需要反向或自定义顺序时,请使用接受 Comparator 的重载方法。

Comparator<Employee> sort = Comparator
    .comparing(Employee::dept)
    .thenComparing(Comparator.comparing(Employee::salary).reversed()); // highest paid first
    // equivalent: .thenComparingDouble(Employee::salary, Comparator.reverseOrder())

按包含空值的字段排序

将 thenComparing 与 nullsLast/nullsFirst 结合使用,即可妥善处理可选字段。

record Product(String name, String category, Double rating) {}

Comparator<Product> sort = Comparator
    .comparing(Product::category, Comparator.nullsLast(Comparator.naturalOrder()))
    .thenComparing(Product::rating,  Comparator.nullsLast(Comparator.reverseOrder()))
    .thenComparing(Product::name);

动态选择排序键

根据用户偏好在运行时选择排序键。

static Comparator<Product> sortBy(String key) {
    return switch (key) {
        case "price"    -> Comparator.comparingDouble(Product::price);
        case "name"     -> Comparator.comparing(Product::name);
        case "stock"    -> Comparator.comparingInt(Product::stock).reversed();
        default         -> Comparator.comparingDouble(Product::price);
    };
}

String userChoice = "stock";
products.stream().sorted(sortBy(userChoice)).forEach(System.out::println);

使用提取函数调用 thenComparing

thenComparing(Function<T,U>) 重载方法会提取一个可比较的键用于次要排序,无需显式指定 Comparator。

record Event(String type, int year, int month, int day) {}

Comparator<Event> chronological = Comparator
    .comparing(Event::type)
    .thenComparingInt(Event::year)
    .thenComparingInt(Event::month)
    .thenComparingInt(Event::day);

对记录类集合进行排序

带有访问器方法的记录类可以在比较器链中无缝使用方法引用。

record Order(String customerId, String status, double total, java.time.LocalDate date) {}

List<Order> orders = loadOrders();

Comparator<Order> byCustomerThenDate = Comparator
    .comparing(Order::customerId)
    .thenComparing(Order::date, Comparator.reverseOrder()); // newest first per customer

orders.stream().sorted(byCustomerThenDate)
    .collect(Collectors.groupingBy(Order::customerId))
    .forEach((cust, ords) -> {
        System.out.println("Customer: " + cust);
        ords.forEach(o -> System.out.println("  " + o.date() + " $" + o.total()));
    });

稳定排序保证

Java 的 List.sort() 和对象数组的 Arrays.sort() 使用稳定的归并排序。比较结果相等的元素会保持原有的相对顺序。

// Stable sort example:
// First sort by name, then by dept
// Equal dept elements preserve the name order from previous sort
List<Employee> sorted = employees.stream()
    .sorted(Comparator.comparing(Employee::name))     // 1st pass
    .sorted(Comparator.comparing(Employee::dept))     // 2nd pass (stable!)
    .collect(Collectors.toList());
// Within each dept, employees remain alphabetically sorted

使用键比较器调用 Comparator.comparing

双参数形式 Comparator.comparing(keyExtractor, keyComparator) 允许您同时指定键及其排序顺序。

// Sort by name length, then alphabetically within same length
Comparator<String> c = Comparator
    .comparing(String::length, Integer::compare)
    .thenComparing(Comparator.naturalOrder());

List<String> words = new ArrayList<>(List.of("fig","apple","kiwi","banana","pea"));
words.sort(c);
System.out.println(words); // [fig, pea, kiwi, apple, banana]

真实场景:航班搜索结果

按价格、时长,然后按出发时间对航班搜索结果进行排序。

record Flight(String airline, double price, int durationMin, java.time.LocalTime departs) {}

Comparator<Flight> bestFlight = Comparator
    .comparingDouble(Flight::price)
    .thenComparingInt(Flight::durationMin)
    .thenComparing(Flight::departs);

收集排序后的结果

将多关键字排序与收集器结合,生成排序后的分组或映射。

Map<String, List<Employee>> byDept = employees.stream()
    .sorted(Comparator.comparing(Employee::dept)
        .thenComparing(Comparator.comparingDouble(Employee::salary).reversed()))
    .collect(Collectors.groupingBy(Employee::dept,
        LinkedHashMap::new,  // preserve insertion (sorted) order
        Collectors.toList()));

byDept.forEach((dept, emps) -> {
    System.out.println(dept + ":");
    emps.forEach(e -> System.out.println("  " + e.name() + " $" + e.salary()));
});

快速检查

您希望先按部门的字母顺序对员工排序,然后按薪资降序排序。哪个链式调用是正确的?

回顾:使用 thenComparing 进行多关键字排序

关键要点:

  • thenComparing() 会添加次要排序关键字,并在主要关键字相等时应用
  • 根据需要串联任意多个 thenComparing 层级
  • 当次要关键字需要降序排列时,将 thenComparing(比较器) 与 reversed() 结合使用
  • 将其与 nullsFirst/nullsLast 结合,用于处理可选字段
  • Java 的 List.sort() 是稳定的——相等元素会保留其原始顺序
  • 动态选择排序:使用 switch 在运行时返回正确的比较器

常见问题解答

「使用 thenComparing 进行多关键字排序」课时是免费的吗?

是的 — 「使用 thenComparing 进行多关键字排序」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「使用 thenComparing 进行多关键字排序」这节课中我会学到什么?

使用 thenComparing 链接比较器,按优先级依次根据多个字段排序 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「使用 thenComparing 进行多关键字排序」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Java Academy 课中编写并运行代码吗?

能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Comparable 接口
  2. Comparator 与 Lambda 排序
  3. 使用 thenComparing 进行多关键字排序
  4. 实际应用中的数组与集合排序
← 返回 Java Academy