杰瑞科技汇

Comparable接口如何实现对象自定义排序?

什么是 Comparable 接口?

Comparable 接口位于 java.lang 包中,它定义了一个对象的“自然排序”(natural ordering),如果一个类实现了 Comparable 接口,就意味着该类的对象之间可以相互比较,并且可以定义一个默认的、内在的排序规则。

Comparable接口如何实现对象自定义排序?-图1
(图片来源网络,侵删)

Comparable 让一个对象“知道”自己应该排在另一个对象的前面还是后面。

Comparable 接口的核心方法

Comparable 接口中只定义了一个方法:

public int compareTo(T o);

这个方法用于“比较当前对象”与“传入对象 o”之间的大小关系。

compareTo 方法的返回值规则:

  • 返回负整数:表示当前对象 小于 传入对象 o,在排序时,当前对象应该排在 o 的前面。
  • 返回零:表示当前对象 等于 传入对象 o,在排序时,它们的相对位置可以任意。
  • 返回正整数:表示当前对象 大于 传入对象 o,在排序时,当前对象应该排在 o 的后面。

一个重要的约定:为了保持排序的一致性,compareTo 方法必须满足以下三个条件(类似 equals 方法的约定):

Comparable接口如何实现对象自定义排序?-图2
(图片来源网络,侵删)
  1. 自反性x.compareTo(x) 必须返回 0。
  2. 对称性x.compareTo(y) 返回 0,y.compareTo(x) 也必须返回 0。
  3. 传递性x.compareTo(y) 返回负数,且 y.compareTo(z) 也返回负数,x.compareTo(z) 必须返回负数。

如何使用 Comparable 接口?(实践示例)

让我们通过一个经典的例子:对一个 Student 类进行排序,我们希望按照学生的年龄进行排序。

步骤 1: 创建 Student 类并实现 Comparable 接口

import java.util.Objects;
// Student 类实现了 Comparable<Student> 接口
// 这里的泛型 <Student> 表示这个类可以和另一个 Student 对象进行比较
public class Student implements Comparable<Student> {
    private String name;
    private int age;
    private int score;
    public Student(String name, int age, int score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
    // Getter 方法
    public String getName() { return name; }
    public int getAge() { return age; }
    public int getScore() { return score; }
    // 实现 compareTo 方法,定义“自然排序”规则
    @Override
    public int compareTo(Student other) {
        // 比较规则:按照年龄升序排序
        // 如果当前对象的年龄小于 other 对象的年龄,返回负数
        if (this.age < other.age) {
            return -1;
        }
        // 如果当前对象的年龄大于 other 对象的年龄,返回正数
        else if (this.age > other.age) {
            return 1;
        }
        // 如果年龄相等,返回 0
        else {
            return 0;
        }
        // 更简洁的写法 (利用 int 的自动减法):
        // return this.age - other.age;
    }
    // 为了方便打印,重写 toString 方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

步骤 2: 使用 Collections.sort() 进行排序

Collections.sort() 方法可以对一个实现了 Comparable 接口的 List 进行排序。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        // 创建一个学生列表
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 22, 90));
        students.add(new Student("Bob", 20, 85));
        students.add(new Student("Charlie", 22, 88));
        students.add(new Student("David", 19, 95));
        System.out.println("排序前的学生列表:");
        for (Student s : students) {
            System.out.println(s);
        }
        // 使用 Collections.sort() 进行排序
        // 因为 Student 类实现了 Comparable,所以可以直接排序
        Collections.sort(students);
        System.out.println("\n按照年龄升序排序后的学生列表:");
        for (Student s : students) {
            System.out.println(s);
        }
    }
}

输出结果:

排序前的学生列表:
Student{name='Alice', age=22, score=90}
Student{name='Bob', age=20, score=85}
Student{name='Charlie', age=22, score=88}
Student{name='David', age=19, score=95}
按照年龄升序排序后的学生列表:
Student{name='David', age=19, score=95}
Student{name='Bob', age=20, score=85}
Student{name='Alice', age=22, score=90}
Student{name='Charlie', age=22, score=88}

从结果可以看到,Student 对象已经按照年龄从小到大成功排序。

Comparable接口如何实现对象自定义排序?-图3
(图片来源网络,侵删)

Comparable vs. Comparator

这是 Java 开发中一个非常常见的面试题,它们都与排序有关,但用途和设计理念不同。

特性 Comparable Comparator
位置 java.lang java.util
定义 在类的内部定义排序规则 在类的外部定义排序规则
方式 类实现 Comparable 接口,重写 compareTo 方法 创建一个独立的比较器类,实现 Comparator 接口,重写 compare 方法
数量 一个类只能有一个 compareTo 方法(即一种自然排序) 一个类可以有多个 Comparator(即多种不同的排序方式)
使用场景 当对象的“自然排序”是明确且固定的时候,字符串按字典序,数字按数值大小。 当需要根据不同标准排序,或者不想修改/不能修改源类的时候。
方法 int compareTo(T o) int compare(T o1, T o2)

Comparator 的示例(延续上面的 Student 类)

假设我们除了想按年龄排序,还想按分数排序,我们不希望修改 Student 类,这时 Comparator 就派上用场了。

import java.util.Comparator;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 22, 90));
        students.add(new Student("Bob", 20, 85));
        students.add(new Student("Charlie", 22, 88));
        students.add(new Student("David", 19, 95));
        // 1. 使用 Comparable 的自然排序(按年龄)
        Collections.sort(students);
        System.out.println("按年龄排序:");
        students.forEach(System.out::println);
        // 2. 使用 Comparator 进行自定义排序(按分数降序)
        // 创建一个匿名内部类作为比较器
        Comparator<Student> scoreComparator = new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                // 降序排序,所以用 s2 减 s1
                return s2.getScore() - s1.getScore();
            }
        };
        // Java 8+ 的 Lambda 表达式写法(更简洁)
        // Comparator<Student> scoreComparator = (s1, s2) -> s2.getScore() - s1.getScore();
        System.out.println("\n按分数降序排序:");
        Collections.sort(students, scoreComparator);
        students.forEach(System.out::println);
    }
}

输出结果:

按年龄排序:
Student{name='David', age=19, score=95}
Student{name='Bob', age=20, score=85}
Student{name='Alice', age=22, score=90}
Student{name='Charlie', age=22, score=88}
按分数降序排序:
Student{name='David', age=19, score=95}
Student{name='Alice', age=22, score=90}
Student{name='Charlie', age=22, score=88}
Student{name='Bob', age=20, score=85}
  • Comparable 是“内比较器”:它让类自己具备可比较性,定义了“自然排序”,当你调用 Collections.sort(list) 且 list 中的元素实现了 Comparable 时,就会使用 compareTo 方法进行排序。
  • Comparator 是“外比较器”:它是一个独立的、外部的排序规则,当你需要多种排序方式,或者不能/不想修改源类时,使用 Comparator,你需要在 Collections.sort(list, comparator) 中显式地传入比较器。
  • 选择哪个?
    • 如果一个类的排序规则是它固有的、唯一的,那么让它实现 Comparable
    • 如果一个类需要多种排序方式,或者排序规则是动态变化的,或者你无法修改源类,那么使用 Comparator

在实际开发中,Comparator 因其灵活性和不污染源代码的特性,使用频率可能更高,尤其是在 Java 8 引入 Lambda 表达式之后,使用 Comparator 变得非常方便和优雅。

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