Java中Comparable和Comparator接口的区别与用法详解

Java中的Comparable和Comparator接口对于对象集合的排序操作非常有用。Java提供了一些内置方法来对原始类型数组、包装类数组或列表进行排序。在本教程中,我们将首先学习如何对原始类型和包装类的数组/列表进行排序,然后使用java.lang.Comparable和java.util.Comparator接口来对自定义类的数组/列表进行排序。让我们通过简单的程序示例来了解如何对原始类型或对象数组和列表进行排序。

package com.Olivia.sort;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class JavaObjectSorting {

    /**
     * 这个类展示了如何排序原始类型数组,
     * 包装类对象数组
     * @param args
     */
    public static void main(String[] args) {
        //排序原始类型数组,如整型数组
        int[] intArr = {5,9,1,10};
        Arrays.sort(intArr);
        System.out.println(Arrays.toString(intArr));
        
        //排序字符串数组
        String[] strArr = {"A", "C", "B", "Z", "E"};
        Arrays.sort(strArr);
        System.out.println(Arrays.toString(strArr));
        
        //排序包装类对象的列表
        List<String> strList = new ArrayList<String>();
        strList.add("A");
        strList.add("C");
        strList.add("B");
        strList.add("Z");
        strList.add("E");
        Collections.sort(strList);
        for(String str: strList) System.out.print(" "+str);
    }
}

以上程序的输出结果为:

[1, 5, 9, 10]
[A, B, C, E, Z]
 A B C E Z

现在让我们来尝试对一个对象数组进行排序。

package com.Olivia.sort;

public class Employee {

    private int id;
    private String name;
    private int age;
    private long salary;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public long getSalary() {
        return salary;
    }

    public Employee(int id, String name, int age, int salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    //重写此方法以便打印员工对象的友好信息
    public String toString() {
        return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" +
                this.salary + "]";
    }

}

这是我用来对员工对象数组进行排序的代码。

//排序对象数组
Employee[] empArr = new Employee[4];
empArr[0] = new Employee(10, "Mikey", 25, 10000);
empArr[1] = new Employee(20, "Arun", 29, 20000);
empArr[2] = new Employee(5, "Lisa", 35, 5000);
empArr[3] = new Employee(1, "Pankaj", 32, 50000);

//使用Comparable接口实现来排序员工数组
Arrays.sort(empArr);
System.out.println("员工列表的默认排序:\n"+Arrays.toString(empArr));

当我尝试运行这段代码时,它抛出了以下运行时异常。

Exception in thread "main" java.lang.ClassCastException: com.Olivia.sort.Employee cannot be cast to java.lang.Comparable
	at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:290)
	at java.util.ComparableTimSort.sort(ComparableTimSort.java:157)
	at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
	at java.util.Arrays.sort(Arrays.java:472)
	at com.Olivia.sort.JavaSorting.main(JavaSorting.java:41)

Comparable与Comparator接口

这是文章《Java示例中的Comparable和Comparator可以比较》的第2部分(共4部分)。

Java提供了Comparable接口,如果我们想要使用Arrays或Collections的排序方法,就需要在自定义类中实现这个接口。Comparable接口具有compareTo(T obj)方法,这个方法会被排序方法使用,您可以查看任何包装类、字符串或日期类来确认这一点。我们应该以这样一种方式重写这个方法,使其返回一个负整数、零或正整数,以指示”this”对象是否小于、等于或大于传递的对象。在Employee类中实现Comparable接口之后,即可得到如下的Employee类。

package com.Olivia.sort;

import java.util.Comparator;

public class Employee implements Comparable<Employee> {

    private int id;
    private String name;
    private int age;
    private long salary;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public long getSalary() {
        return salary;
    }

    public Employee(int id, String name, int age, int salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    public int compareTo(Employee emp) {
        //让我们根据id对员工进行升序排序
        //当此员工id小于、等于或大于指定对象时,
        //返回负整数、零或正整数
        return (this.id - emp.id);
    }

    @Override
    //这是打印员工友好信息所必需的
    public String toString() {
        return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" +
                this.salary + "]";
    }

}

现在当我们执行上面的代码片段对雇员数组进行排序并打印时,以下是输出结果。

Employees列表的默认排序:
[[id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000]]

正如您所看到的,Employees数组按照ID升序排序。但在大多数实际情况下,我们希望根据不同的参数进行排序。例如,作为CEO,我希望根据薪资对员工进行排序,而HR希望根据年龄进行排序。这种情况下,我们需要使用Java的Comparator接口,因为Comparable.compareTo(Object o)方法的实现只能提供默认排序,而且我们无法动态地改变它。而使用Comparator,我们可以定义多个不同的排序方式,然后根据需求选择适当的排序方法。

Java的比较器(Comparator)

需要实现Comparator接口的compare(Object o1, Object o2)方法,该方法接受两个Object参数。它应该这样实现:如果第一个参数小于第二个参数,返回负整数;如果它们相等,返回零;如果第一个参数大于第二个参数,返回正整数。Comparable和Comparator接口使用泛型用于编译时类型检查,可以了解更多关于Java泛型的知识。下面是如何在Employee类中创建不同的Comparator实现的方法。

/**
     * 按薪资排序员工列表或数组的比较器
     */
    public static Comparator<Employee> SalaryComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return (int) (e1.getSalary() - e2.getSalary());
        }
    };

    /**
     * 按年龄排序员工列表或数组的比较器
     */
    public static Comparator<Employee> AgeComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return e1.getAge() - e2.getAge();
        }
    };

    /**
     * 按姓名排序员工列表或数组的比较器
     */
    public static Comparator<Employee> NameComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return e1.getName().compareTo(e2.getName());
        }
    };

上述所有的Comparator接口的实现都是匿名类。我们可以使用这些比较器将参数传递给Arrays和Collections类的排序函数。

//使用比较器按薪资排序员工数组
Arrays.sort(empArr, Employee.SalaryComparator);
System.out.println("按薪资排序的员工列表:\n"+Arrays.toString(empArr));

//使用比较器按年龄排序员工数组
Arrays.sort(empArr, Employee.AgeComparator);
System.out.println("按年龄排序的员工列表:\n"+Arrays.toString(empArr));

//使用比较器按姓名排序员工数组
Arrays.sort(empArr, Employee.NameComparator);
System.out.println("按姓名排序的员工列表:\n"+Arrays.toString(empArr));

以下是上述代码片段的输出结果。

按薪资排序的员工列表:
[[id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000], [id=1, name=Pankaj, age=32, salary=50000]]
按年龄排序的员工列表:
[[id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000], [id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000]]
按姓名排序的员工列表:
[[id=20, name=Arun, age=29, salary=20000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=1, name=Pankaj, age=32, salary=50000]]

所以现在我们知道如果我们想要对Java对象数组或列表进行排序,我们需要实现Java的Comparable接口来提供默认排序,而且我们应该实现Java的Comparator接口来提供不同的排序方式。我们还可以创建一个单独的类来实现Comparator接口,然后使用它。这里是我们最终的类,解释了Java中的Comparable和Comparator。

package com.Olivia.sort;

import java.util.Comparator;

public class Employee implements Comparable<Employee> {

    private int id;
    private String name;
    private int age;
    private long salary;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public long getSalary() {
        return salary;
    }

    public Employee(int id, String name, int age, int salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    public int compareTo(Employee emp) {
        //让我们按升序对员工ID进行排序
        //当此员工ID小于、等于或大于指定对象时,
        //返回负整数、零或正整数。
        return (this.id - emp.id);
    }

    @Override
    //这是打印员工友好信息所必需的
    public String toString() {
        return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" +
                this.salary + "]";
    }

    /**
     * 按薪资排序员工列表或数组的比较器
     */
    public static Comparator<Employee> SalaryComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return (int) (e1.getSalary() - e2.getSalary());
        }
    };

    /**
     * 按年龄排序员工列表或数组的比较器
     */
    public static Comparator<Employee> AgeComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return e1.getAge() - e2.getAge();
        }
    };

    /**
     * 按姓名排序员工列表或数组的比较器
     */
    public static Comparator<Employee> NameComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return e1.getName().compareTo(e2.getName());
        }
    };
}

以下是Comparator接口的单独类实现,它将首先比较两个员工对象的ID,如果相同,则再比较它们的姓名。

package com.Olivia.sort;

import java.util.Comparator;

public class EmployeeComparatorByIdAndName implements Comparator<Employee> {

    @Override
    public int compare(Employee o1, Employee o2) {
        int flag = o1.getId() - o2.getId();
        if(flag==0) flag = o1.getName().compareTo(o2.getName());
        return flag;
    }

}

这是测试类,我们在java中使用Comparable和Comparator的不同方式来对对象进行排序。

package com.Olivia.sort;

import java.util.Arrays;

public class JavaObjectSorting {

    /**
     * 这个类展示了如何通过实现Comparable和Comparator接口
     * 来对自定义对象数组/列表进行排序
     * @param args
     */
    public static void main(String[] args) {

        //排序自定义对象数组
        Employee[] empArr = new Employee[4];
        empArr[0] = new Employee(10, "Mikey", 25, 10000);
        empArr[1] = new Employee(20, "Arun", 29, 20000);
        empArr[2] = new Employee(5, "Lisa", 35, 5000);
        empArr[3] = new Employee(1, "Pankaj", 32, 50000);
        
        //使用Comparable接口实现排序员工数组
        Arrays.sort(empArr);
        System.out.println("员工列表的默认排序:\n"+Arrays.toString(empArr));
        
        //使用比较器按薪资排序员工数组
        Arrays.sort(empArr, Employee.SalaryComparator);
        System.out.println("按薪资排序的员工列表:\n"+Arrays.toString(empArr));
        
        //使用比较器按年龄排序员工数组
        Arrays.sort(empArr, Employee.AgeComparator);
        System.out.println("按年龄排序的员工列表:\n"+Arrays.toString(empArr));
        
        //使用比较器按姓名排序员工数组
        Arrays.sort(empArr, Employee.NameComparator);
        System.out.println("按姓名排序的员工列表:\n"+Arrays.toString(empArr));
        
        //使用比较器类按ID和姓名排序员工列表
        empArr[0] = new Employee(1, "Mikey", 25, 10000);
        Arrays.sort(empArr, new EmployeeComparatorByIdAndName());
        System.out.println("按ID和姓名排序的员工列表:\n"+Arrays.toString(empArr));
    }

}

以下是上述程序的输出结果。

员工列表的默认排序:
[[id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000]]
按薪资排序的员工列表:
[[id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000], [id=1, name=Pankaj, age=32, salary=50000]]
按年龄排序的员工列表:
[[id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000], [id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000]]
按姓名排序的员工列表:
[[id=20, name=Arun, age=29, salary=20000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=1, name=Pankaj, age=32, salary=50000]]
按ID和姓名排序的员工列表:
[[id=1, name=Mikey, age=25, salary=10000], [id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000]]

java.lang.Comparable和java.util.Comparator是强大的接口,可用于在Java中提供对象的排序。

可比性与比较器

  1. 可比对接口可用于提供单一的排序方式,而比较器接口用于提供不同的排序方式。
  2. 使用可比对接口,类需要实现它,而使用比较器不需要对类进行任何更改。
  3. 可比对接口在java.lang包中,而比较器接口在java.util包中。
  4. 对于使用可比对接口,我们不需要在客户端代码中进行任何更改,Arrays.sort()或Collection.sort()方法会自动使用类的compareTo()方法。对于比较器,客户端需要提供比较器类来在compare()方法中使用。

你知道Collections.sort()方法是基于策略模式的吗?

你可以从我们的GitHub存储库中获取完整的代码和更多Java核心示例。

bannerAds