杰瑞科技汇

Java排序中Comparator如何灵活使用?

目录

  1. 核心概念:Comparator 是什么?
  2. 为什么需要 Comparator (与 Comparable 的对比)
  3. 如何使用 Comparator
    • 传统匿名内部类方式
    • Java 8+ 的 Lambda 表达式 (推荐)
    • Java 8+ 的 Comparator 静态方法链式调用 (最现代、最推荐)
  4. 实战案例:对复杂对象列表进行排序
  5. Comparator 常用静态方法详解

核心概念:Comparator 是什么?

Comparator 是一个函数式接口(在 Java 8 中被标注为 @FunctionalInterface),它位于 java.util 包下。

Java排序中Comparator如何灵活使用?-图1
(图片来源网络,侵删)

它的核心作用是定义一个临时的、外部的比较规则,你可以把它想象成一个“裁判”,这个裁判知道如何比较两个任意对象的大小,然后告诉你谁大、谁小,或者相等。

Comparator 接口中最核心的方法是 compare(T o1, T o2)

int compare(T o1, T o2);
  • 返回值:
    • 负整数 (o1 - o2 < 0):表示 o1 小于 o2
    • (o1 - o2 == 0):表示 o1 等于 o2
    • 正整数 (o1 - o2 > 0):表示 o1 大于 o2

为什么需要 Comparator? (与 Comparable 的对比)

要理解 Comparator 的强大,首先要了解它的“兄弟” Comparable

Comparable (内部比较规则)

  • 定义:当一个类实现了 Comparable 接口,它就拥有了内在的、默认的比较规则。
  • 位置:比较逻辑写在要比较的类本身内部。
  • 方法int compareTo(T o);
  • 例子String 类、Integer 类、Double 类都实现了 Comparable,所以它们可以直接使用 Collections.sort()Arrays.sort() 进行排序。
// String 类内部定义了如何比较两个字符串
String s1 = "apple";
String s2 = "banana";
// s1.compareTo(s2) 会返回一个负数,因为 "apple" < "banana"
int result = s1.compareTo(s2); 

局限性Comparable 的规则是固定的。String 类默认按字典序排序,如果你想按字符串长度排序,就必须去修改 String 类的源码,这显然是不可能的。

Java排序中Comparator如何灵活使用?-图2
(图片来源网络,侵删)

Comparator (外部比较规则)

  • 定义Comparator 是一个外部的、临时的比较规则。
  • 位置:比较逻辑写在要比较的类之外,可以随时创建和修改。
  • 优点
    1. 灵活性:同一个类的对象,可以根据需要使用多种不同的排序规则(按姓名、按年龄、按薪资等)。
    2. 解耦:业务逻辑与排序规则分离,代码更清晰。
    3. 不侵入性:你不需要修改被排序的类的源码(比如一些第三方库的类,你无法修改其源码)。

| 特性 | Comparable | Comparator | | :--- | :--- | :--- | | 比较规则位置 | 在类内部实现 | 在类外部定义 | | 数量 | 一个类只能有一个默认规则 | 可以有任意多个不同规则 | | 修改方式 | 需要修改源码 | 无需修改源码,随时创建 | | 核心方法 | compareTo(T o) | compare(T o1, T o2) | | 适用场景 | 定义了该对象的“自然排序” (Natural Ordering) | 需要多种排序方式,或无法修改源码时 |


如何使用 Comparator

假设我们有一个 Person 类,我们想对 Person 对象列表进行排序。

class Person {
    private String name;
    private int age;
    private double salary;
    // 构造器、getters、toString() 省略...
    public Person(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    public String getName() { return name; }
    public int getAge() { return age; }
    public double getSalary() { return salary; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", salary=" + salary + "}";
    }
}

我们有 List<Person> people 列表,我们想对它进行排序。

传统匿名内部类

这种方式在 Java 8 之前很常见,但代码比较冗长。

Java排序中Comparator如何灵活使用?-图3
(图片来源网络,侵删)
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
List<Person> people = new ArrayList<>();
// ... 添加一些 Person 对象
// 1. 按年龄升序排序
Collections.sort(people, new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        return Integer.compare(p1.getAge(), p2.getAge());
    }
});
// 2. 按薪资降序排序
Collections.sort(people, new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        // 注意:降序,所以是 p2 - p1
        return Double.compare(p2.getSalary(), p1.getSalary());
    }
});

Java 8+ 的 Lambda 表达式 (推荐)

Comparator 是一个函数式接口,所以我们可以用更简洁的 Lambda 表达式来代替匿名内部类。

语法:(o1, o2) -> { return ...; },如果方法体只有一行,return 和 也可以省略。

import java.util.List;
List<Person> people = new ArrayList<>();
// ... 添加一些 Person 对象
// 1. 按年龄升序排序
people.sort((p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));
// 2. 按薪资降序排序
people.sort((p1, p2) -> Double.compare(p2.getSalary(), p1.getSalary()));
// 3. 按姓名字典序排序 (String 已经实现了 Comparable)
people.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));

注意:从 Java 8 开始,List 接口本身提供了 sort(Comparator<? super E> c) 方法,所以可以直接对 List 调用 sort(),而不需要像以前那样用 Collections.sort(list)

Java 8+ 的 Comparator 静态方法链式调用 (最现代、最推荐)

这是目前最流行、最易读、最强大的方式,它利用了 Comparator 接口在 Java 8 中新增的一系列静态方法和默认方法,可以像流式 API 一样链式调用。

import java.util.List;
import java.util.Comparator;
List<Person> people = new ArrayList<>();
// ... 添加一些 Person 对象
// 1. 按年龄升序 (推荐使用 Comparator.comparing)
people.sort(Comparator.comparing(Person::getAge));
// 2. 按薪资降序 (使用 thenComparing 或 reversed())
people.sort(Comparator.comparing(Person::getSalary).reversed());
// 3. 先按年龄升序,如果年龄相同,再按薪资降序 (链式调用)
people.sort(
    Comparator.comparing(Person::getAge)
              .thenComparing(Person::getSalary, Comparator.reverseOrder())
);
// 4. 先按姓氏排序,再按名字排序 (多级比较)
// 假设 Person 有 getLastName() 和 getFirstName() 方法
people.sort(
    Comparator.comparing(Person::getLastName)
              .thenComparing(Person::getFirstName)
);
  • Comparator.comparing(Function<? super T, ? extends U> keyExtractor):核心方法,用于提取排序键。
  • Comparator.reverseOrder() / Comparator.naturalOrder():直接生成一个反转或自然的比较器。
  • thenComparing(...):用于定义多级排序。
  • reversed():将当前比较器反转。

实战案例:对复杂对象列表进行排序

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Charlie", 30, 75000.0));
        people.add(new Person("Alice", 25, 85000.0));
        people.add(new Person("Bob", 30, 65000.0));
        people.add(new Person("David", 22, 55000.0));
        System.out.println("原始列表: " + people);
        // 1. 按年龄升序
        people.sort(Comparator.comparing(Person::getAge));
        System.out.println("\n按年龄升序: " + people);
        // 2. 按薪资降序
        people.sort(Comparator.comparing(Person::getSalary).reversed());
        System.out.println("\n按薪资降序: " + people);
        // 3. 先按年龄升序,再按薪资降序 (年龄相同时,薪资高的排前面)
        people.sort(
            Comparator.comparing(Person::getAge)
                      .thenComparing(Person::getSalary, Comparator.reverseOrder())
        );
        System.out.println("\n按年龄升序,再按薪资降序: " + people);
        // 4. 按姓名长度排序
        people.sort(Comparator.comparing(p -> p.getName().length()));
        System.out.println("\n按姓名长度升序: " + people);
    }
}

Comparator 常用静态方法详解

方法 描述 示例
comparing(...) 核心方法,基于某个属性(函数)创建比较器。 Comparator.comparing(Person::getAge)
comparingInt(...) 专门为 int 类型的属性优化,避免自动装箱。 Comparator.comparingInt(Person::getAge)
comparingLong(...) 专门为 long 类型的属性优化。 Comparator.comparingLong(Person::getId)
comparingDouble(...) 专门为 double 类型的属性优化。 Comparator.comparingDouble(Person::getSalary)
naturalOrder() 生成一个按照元素“自然排序”的比较器(要求元素实现了 Comparable)。 Comparator.naturalOrder()
reverseOrder() 生成一个与“自然排序”相反的比较器。 Comparator.reverseOrder()
nullsFirst(...) null 值视为“最小”值,放在列表最前面,需要一个 Comparator 来比较非 null 元素。 Comparator.nullsFirst(Comparator.comparing(Person::getName))
nullsLast(...) null 值视为“最大”值,放在列表最后面,需要一个 Comparator 来比较非 null 元素。 Comparator.nullsLast(Comparator.comparing(Person::getName))
thenComparing(...) 多级排序,当前比较器结果相同时,使用下一个比较器。 c1.thenComparing(c2)
reversed() 反转当前比较器的排序结果。 c.reversed()

  • Comparator 是 Java 排序的瑞士军刀,提供了无与伦比的灵活性。
  • 当你需要多种排序方式,或者无法修改被排序类的源码时,Comparator 是你的不二之选。
  • 推荐使用 Java 8+ 的新特性:优先使用 Comparator.comparing() 和 Lambda 表达式,代码更简洁、更易读、更不易出错。
  • 掌握 Comparator 的链式调用(comparing(), thenComparing(), reversed())是编写复杂排序逻辑的关键。

希望这份详细的指南能帮助你彻底理解并掌握 Java 中的 Comparator

分享:
扫描分享到社交APP
上一篇
下一篇