问题

Comparator 与 Comparable 有何区别?

答案

核心概念

  • Comparable:内部比较器,定义对象的自然排序(默认排序规则)
    • 实现类:class User implements Comparable<User>
    • 方法:int compareTo(T o)
    • 特点:侵入式,需要修改原类
  • Comparator:外部比较器,定义对象的定制排序(临时排序规则)
    • 实现类:class AgeComparator implements Comparator<User>
    • 方法:int compare(T o1, T o2)
    • 特点:非侵入式,无需修改原类

1. Comparable 接口

基本用法

public class Student implements Comparable<Student> {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    // 定义自然排序:按分数升序
    @Override
    public int compareTo(Student other) {
        return this.score - other.score;
        // 返回值:
        // < 0:this < other(this 排在前面)
        // = 0:this == other(相等)
        // > 0:this > other(this 排在后面)
    }

    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("张三", 85),
            new Student("李四", 92),
            new Student("王五", 78)
        );

        // 使用自然排序
        Collections.sort(students);
        // 结果:王五(78), 张三(85), 李四(92)
    }
}

JDK 中的典型实现

// 1. Integer 的自然排序:数值升序
Integer i1 = 10;
Integer i2 = 20;
System.out.println(i1.compareTo(i2));  // -1(i1 < i2)

// 2. String 的自然排序:字典序
String s1 = "apple";
String s2 = "banana";
System.out.println(s1.compareTo(s2));  // -1(a < b)

// 3. Date 的自然排序:时间先后
Date d1 = new Date(2024, 1, 1);
Date d2 = new Date(2024, 12, 31);
System.out.println(d1.compareTo(d2));  // -1(d1 早于 d2)

注意事项:避免溢出

public class BadComparable implements Comparable<BadComparable> {
    private int value;

    @Override
    public int compareTo(BadComparable other) {
        // 错误:可能溢出
        return this.value - other.value;
        // 例如:value = Integer.MAX_VALUE, other.value = -1
        // 结果:Integer.MAX_VALUE - (-1) = 溢出为负数
    }
}

public class GoodComparable implements Comparable<GoodComparable> {
    private int value;

    @Override
    public int compareTo(GoodComparable other) {
        // 正确:使用 Integer.compare()
        return Integer.compare(this.value, other.value);

        // 或者使用三元运算符
        // return this.value < other.value ? -1 : (this.value == other.value ? 0 : 1);
    }
}

2. Comparator 接口

基本用法

public class Student {
    private String name;
    private int score;
    private int age;

    // 无需实现 Comparable
}

// 定制比较器1:按分数降序
public class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return Integer.compare(s2.score, s1.score);  // 降序
    }
}

// 定制比较器2:按年龄升序
public class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return Integer.compare(s1.age, s2.age);  // 升序
    }
}

public class Test {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("张三", 85, 20),
            new Student("李四", 92, 19),
            new Student("王五", 78, 21)
        );

        // 使用不同的比较器
        Collections.sort(students, new ScoreComparator());
        // 结果:李四(92), 张三(85), 王五(78)

        Collections.sort(students, new AgeComparator());
        // 结果:李四(19), 张三(20), 王五(21)
    }
}

Lambda 表达式简化(Java 8+)

public class Test {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("张三", 85, 20),
            new Student("李四", 92, 19),
            new Student("王五", 78, 21)
        );

        // 1. Lambda 表达式
        students.sort((s1, s2) -> Integer.compare(s2.getScore(), s1.getScore()));

        // 2. 方法引用 + Comparator 工具方法
        students.sort(Comparator.comparing(Student::getScore).reversed());

        // 3. 多字段排序:先按分数降序,再按年龄升序
        students.sort(Comparator.comparing(Student::getScore).reversed()
                                .thenComparing(Student::getAge));

        // 4. 处理 null 值
        students.sort(Comparator.nullsFirst(
            Comparator.comparing(Student::getName)
        ));
    }
}

3. Comparator 的高级用法

1. 静态工厂方法(Java 8+)

public class ComparatorDemo {
    public static void main(String[] args) {
        List<Student> students = getStudents();

        // 1. comparing:提取比较键
        students.sort(Comparator.comparing(Student::getScore));

        // 2. comparingInt/Long/Double:避免装箱
        students.sort(Comparator.comparingInt(Student::getScore));

        // 3. reversed:反转排序
        students.sort(Comparator.comparing(Student::getScore).reversed());

        // 4. thenComparing:多级排序
        students.sort(Comparator.comparing(Student::getScore)
                                .thenComparing(Student::getName));

        // 5. nullsFirst/nullsLast:null 值处理
        students.sort(Comparator.nullsFirst(
            Comparator.comparing(Student::getName)
        ));

        // 6. naturalOrder/reverseOrder:自然排序
        List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5);
        numbers.sort(Comparator.naturalOrder());        // 升序
        numbers.sort(Comparator.reverseOrder());        // 降序
    }
}

2. 复杂排序场景

public class ComplexSortDemo {
    public static void main(String[] args) {
        List<Employee> employees = getEmployees();

        // 场景1:先按部门升序,再按薪资降序,最后按姓名升序
        employees.sort(Comparator.comparing(Employee::getDepartment)
                                 .thenComparing(Employee::getSalary, Comparator.reverseOrder())
                                 .thenComparing(Employee::getName));

        // 场景2:自定义比较逻辑
        employees.sort(Comparator.comparing(e -> {
            // VIP 员工优先
            return e.isVip() ? 0 : 1;
        }).thenComparing(Employee::getSalary, Comparator.reverseOrder()));

        // 场景3:忽略大小写排序
        List<String> names = Arrays.asList("Alice", "bob", "Charlie");
        names.sort(String.CASE_INSENSITIVE_ORDER);
    }
}

4. 对比总结

特性 Comparable Comparator
包路径 java.lang.Comparable java.util.Comparator
方法 int compareTo(T o) int compare(T o1, T o2)
实现位置 在被比较的类内部 在独立的比较器类中
排序类型 自然排序(默认排序) 定制排序(临时排序)
侵入性 侵入式(需修改原类) 非侵入式(无需修改原类)
灵活性 一个类只能有一种自然排序 可以有多个不同的比较器
使用场景 对象有明确的默认排序规则 需要多种排序方式或无法修改原类
典型应用 Integer, String, Date 等 Collections.sort(), Arrays.sort()

5. 使用场景选择

使用 Comparable 的场景

// 1. 对象有明确的自然排序
public class Product implements Comparable<Product> {
    private String id;
    private double price;

    @Override
    public int compareTo(Product other) {
        // 商品默认按价格升序排序
        return Double.compare(this.price, other.price);
    }
}

// 2. 类的设计者可以控制排序规则
// 例如:JDK 的 Integer, String, Date 等

使用 Comparator 的场景

// 1. 无法修改原类(第三方库、JDK 类)
List<String> strings = Arrays.asList("apple", "banana", "cherry");
strings.sort(Comparator.comparing(String::length));  // 按长度排序

// 2. 需要多种排序方式
List<Student> students = getStudents();
students.sort(new ScoreComparator());  // 按分数排序
students.sort(new AgeComparator());    // 按年龄排序
students.sort(new NameComparator());   // 按姓名排序

// 3. 临时排序需求
students.sort((s1, s2) -> s1.getName().compareTo(s2.getName()));

// 4. 复杂的多字段排序
students.sort(Comparator.comparing(Student::getGrade)
                        .thenComparing(Student::getScore)
                        .thenComparing(Student::getName));

6. 实际应用案例

案例1:TreeSet 的排序

public class TreeSetDemo {
    public static void main(String[] args) {
        // 方式1:使用 Comparable(自然排序)
        Set<Integer> set1 = new TreeSet<>();
        set1.addAll(Arrays.asList(5, 2, 8, 1, 9));
        System.out.println(set1);  // [1, 2, 5, 8, 9]

        // 方式2:使用 Comparator(定制排序)
        Set<Integer> set2 = new TreeSet<>(Comparator.reverseOrder());
        set2.addAll(Arrays.asList(5, 2, 8, 1, 9));
        System.out.println(set2);  // [9, 8, 5, 2, 1]
    }
}

案例2:PriorityQueue 的排序

public class PriorityQueueDemo {
    public static void main(String[] args) {
        // 默认:小顶堆(使用 Comparable)
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        minHeap.addAll(Arrays.asList(5, 2, 8, 1, 9));
        System.out.println(minHeap.poll());  // 1

        // 大顶堆(使用 Comparator)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
        maxHeap.addAll(Arrays.asList(5, 2, 8, 1, 9));
        System.out.println(maxHeap.poll());  // 9
    }
}

案例3:Stream 排序

public class StreamSortDemo {
    public static void main(String[] args) {
        List<Student> students = getStudents();

        // 使用 Comparable
        List<Integer> scores = students.stream()
            .map(Student::getScore)
            .sorted()  // 自然排序
            .collect(Collectors.toList());

        // 使用 Comparator
        List<Student> sorted = students.stream()
            .sorted(Comparator.comparing(Student::getScore).reversed())
            .collect(Collectors.toList());
    }
}

7. 性能优化建议

public class PerformanceDemo {
    // 1. 避免重复计算
    // 不推荐
    list.sort((s1, s2) -> s1.getName().toLowerCase().compareTo(s2.getName().toLowerCase()));

    // 推荐:提前计算
    list.sort(Comparator.comparing(s -> s.getName().toLowerCase()));

    // 2. 使用基本类型比较器(避免装箱)
    // 不推荐
    list.sort(Comparator.comparing(Student::getScore));

    // 推荐
    list.sort(Comparator.comparingInt(Student::getScore));

    // 3. 缓存比较器实例
    private static final Comparator<Student> SCORE_COMPARATOR =
        Comparator.comparingInt(Student::getScore);

    list.sort(SCORE_COMPARATOR);
}

答题总结

面试要点

  1. 核心区别
    • Comparable 是内部比较器,定义自然排序,需修改原类
    • Comparator 是外部比较器,定义定制排序,无需修改原类
  2. 方法签名
    • Comparable:int compareTo(T o)(单参数)
    • Comparator:int compare(T o1, T o2)(双参数)
  3. 使用场景
    • Comparable:对象有明确的默认排序规则(如 Integer, String)
    • Comparator:需要多种排序方式或无法修改原类
  4. 最佳实践
    • 避免使用减法比较(防止溢出),使用 Integer.compare()
    • Java 8+ 推荐使用 Lambda 和 Comparator.comparing()
    • 多字段排序使用 thenComparing()
    • 基本类型使用 comparingInt/Long/Double 避免装箱

记忆口诀

  • Comparable 内部定,一个类一种排序
  • Comparator 外部传,灵活多变随意换