当前位置: 代码迷 >> 综合 >> Java 基础之 Comparable Comparator
  详细解决方案

Java 基础之 Comparable Comparator

热度:11   发布时间:2023-12-12 16:33:24.0

一、概述

本篇文章是对 Java 中两个用于比较排序的接口 Comparable 和 Comparator 使用方式的简单介绍,然后会通过举例子的方式来说明它们各自的使用方法,首先我们来看看 Comparable 接口的相关介绍。


二、Comparable 接口

先下结论:Comparable 的中文意思是“可比较的”,也就是说,实现了该接口的类是可排序的

例如我们自定义一个简单的 Student 类:

public class Student {
    private int score;   // 学生成绩private String name; // 学生名字public Student(int score, String name) {
    this.score = score;this.name = name;}
}

假设我们现在需要将一个班的学生成绩从低到高进行排序,当两个学生成绩相同时则按照名字的先后顺序进行排序,如下所示:

zhangsan   100
lisi               60
wangwu      80
marck         80

这四个学生的经过排序后,应当输出如下:

lisi            60
marck       80
wangwu    80
zhangsan 100

可以看到成绩是按照从低到高排序的,而 wangwu 和 marck 由于成绩都是80分,所以继续比较他们的名字,由于 marck 的开头字母 “m” 比 wangwu 的开头字母 “w” 小,所以 marck 排在 wangwu 的前面。

那么,我们应当如何定义这种排序的方式呢?答案就是让 Student 类实现 Comparable 接口。我们首先来看到这个接口是如何定义的:

public interface Comparable<T> {
    int compareTo(T o);
}

可以看到这个接口中仅仅定义了一个 compareTo() 方法,假设我们通过 x.compareTo(y) 来比较 x 和 y 的大小,那么:

  • 若该方法返回负数,说明 x 比 y 小;
  • 若该方法返回零,说明 x 和 y 相等;
  • 若该方法返回正数,说明 x 比 y 大。

所以我们可以将 Student 类改造如下:

public class Student implements Comparable<Student>{
    private int score;private String name;public Student(int score, String name) {
    this.score = score;this.name = name;}@Overridepublic int compareTo(Student s) {
    // 调用方式假设为 x.compareTo(y) 的形式// x.score > y.score,返回1if (this.score > s.score){
    return 1;} else if (this.score < s.score){
      // x.score < y.score,返回 -1return -1;} else {
    // x.score = y.score 时进一步比较 name 的大小排序return this.name.compareTo(s.name);}}@Overridepublic String toString() {
    return this.name + " " + this.score;}public static void main(String[] args) {
    Student[] students = new Student[4];students[0] = new Student(100, "zhangsan");students[1] = new Student(60, "lisi");students[2] = new Student(80, "wangwu");students[3] = new Student(80, "marck");Arrays.sort(students);for (Student s : students){
    System.out.println(s);}}
}

从上面改造后的 Student 类中可以看到,compareTo() 方法先是对成绩的大小进行比较,大于0直接返回1,小于0直接返回 -1。如果成绩相等,则会进一步比较两个 Student 对象的 name,由于 name 是 String 类型的,而 String 中早已为我们实现了 Comparable 接口,所以我们直接调用它的 compareTo() 方法返回即可。

在 main 方法中我们对其做一个简单的测试,通过 Arrays.sort() 方法对数组 students 进行排序,输出结果如下:

lisi 60
marck 80
wangwu 80
zhangsan 100

可以看到和我们的预期结果是一致的。

注意:无论是对数组进行排序的 Arrays.sort() 方法还是用于对 List 进行排序的 Collections.sort() 方法,它们都是按照从小到大的顺序对数组进行排序的,这个大小顺序由 compareTo() 方法的返回值进行确定

对于上面这段话的理解,我们通过一个例子来理解:对于学生成绩的排名,从低到高排并不符合我们的习惯,我们都是习惯从高到底排的,那么我们应当如何对上面的 Student 类做进一步修改呢?

其实很简单,我们只需要让 Arrays.sort() 方法觉得成绩大的数是比较“小”的就行了,因为这个方法是按照从小到达的顺序进行排序的,修改后的 compareTo() 方法如下所示:

@Override
public int compareTo(Student s) {
    // 调用方式假设为 x.compareTo(y) 的形式// x.score < y.score,返回1if (this.score < s.score){
    return 1;} else if (this.score > s.score){
      // x.score > y.score,返回 -1return -1;} else {
    // x.score = y.score 时进一步比较 name 的大小排序return this.name.compareTo(s.name);}
}

可以看到其实就是把大于小于两种情况的返回值做了下对调,修改过后输出结果如下所示:

zhangsan 100
marck 80
wangwu 80
lisi 60

可以看到输出结果中,成绩按照从高到底进行排序,但是名字仍然按照由小到大排序。

总结一下 Comparable 接口:

  • 实现了该接口的类即表示可排序的。
  • compareTo() 方法即为定义两个对象如何比较大小的方法,返回值大于、小于、等于 0 的情况分别表示调用者大于、等于、小于方法的参数。

三、Comparator 接口

Comparator 翻译为比较器,也就是说它类似于是一种用于比较的工具。我们重新回顾一下原始的 Student 类:

public class Student {
    private int score;   // 学生成绩private String name; // 学生名字public Student(int score, String name) {
    this.score = score;this.name = name;}public int getScore() {
    return score;}public String getName() {
    return name;}
}

为了让 Student 变为可排序的类,我们让其实现了 Comparable 接口,同时实现了 compareTo() 方法,在这个方法中定义了大小关系的判断。这是在一个类的内部实现可比较的效果。

但是如果我们在设计类的时候没有考虑到为其实现 Comparable 接口,但是现在我们又需要对这些对象进行排序,这时我们就可以考虑使用比较器 Comparator,它相当于一个比较的工具,在我们类的外部定义对象之间大小关系的比较,首先我们看到它的定义:

public interface Comparator<T> {
    int compare(T o1, T o2);boolean equals(Object o);
}

可以看到 Comparator 定义了两个接口方法,其中我们必须实现的是 compare() 方法,equals() 方法可以不实现,因为所有的类都继承自 Object,而 Object 中提供了默认的 equals() 方法实现。接下来重点来看到 compare() 方法,可以看到它有两个参数 o1 和 o2,它的返回值定义如下:

  • 如果返回值大于零,说明 o1 > o2;
  • 如果返回值小于零,说明 o1 < o2;
  • 如果返回值等于零,说明 o1 = o2。

接下来我们定义一个 Student 类的比较器,来辅助 Student 的比较:

public class StudentComparator implements Comparator<Student> {
    @Overridepublic int compare(Student s1, Student s2) {
    if (s1.getScore() < s2.getScore()) {
    return 1;} else if (s1.getScore() > s2.getScore()){
    return -1;} else {
    return s1.getName().compareTo(s2.getName());}}
}

这里我们仍然按照分数从高到低排序,相同成绩的名字从小到达排序的排序方式进行定义。

需要注意的是比较器是在类的外部对类进行比较的,所以对于私有的字段我们得添加相应的 getter 以便我们获得其值。接下来我们看到它的使用(注意下面例子的 Student 类没有实现 Comparable 接口):

public class Test {
    public static void main(String[] args) {
    List<Student> students = new ArrayList<>(4);students.add(new Student(100, "zhangsan"));students.add(new Student(60, "lisi"));students.add(new Student(80, "wangwu"));students.add(new Student(80, "marck"));Collections.sort(students, new StudentComparator());for (Student s : students){
    System.out.println(s);}}
}

这里我们是通过 List 的形式存储学生的数据,然后通过调用 Collections.sort() 方法来对其进行排序的。输出结果如下所示:

zhangsan 100
marck 80
wangwu 80
lisi 60

可以看到通过 Comparator 我们能够实现同样的排序效果。接下来我们再举一个例子,将优先队列 PriorityQueue 从默认的最小堆改为最大堆的形式。

PriorityQueue 有一个构造方法可以传入比较器 Comparator,如下所示:

public PriorityQueue(Comparator<? super E> comparator);

这个构造方法传入的比较器用于定义优先队列中的元素的大小关系,而 PriorityQueue 的默认实现是最小优先队列,那么我们转换一下思路,将大小关系颠倒一下,就可以实现最大堆了,代码如下所示:

public static void main(String[] args) {
    PriorityQueue<String> maxHeap = new PriorityQueue<String>(new Comparator<String>() {
    @Overridepublic int compare(String s1, String s2) {
    return s2.compareTo(s1);}});
}

这里我们以 String 类型的元素为例,在定义的比较器中,我们直接返回 s2.compareTo(s1) 就能实现最大堆的效果。


四、总结

对于 Comparable 和 Comparator 的联系和区别,总结如下:

  • Comparable 和 Comparator 接口均为定义比较相关的接口。
  • Comparable 接口用在类的“内部”,实现该接口的类表示为可排序的。
  • Comparator 接口用在类的“外部”,对于没有实现 Comparable 接口的类,我们可以通过对其提供一个比较器来对该类进行比较。
  相关解决方案