ITmob-Ly
发布于 2024-02-02 / 131 阅读
0

Comparable 和 Comparator 之间的差异,如何以及何时使用它们?

Java提供了 ComparableComparator 两个接口,为我们提供了为对象自定义排序逻辑的能力。本文中,我们将探讨 ComparableComparator 之间的差异,何时如何使用它们。

Comparable 接口:

如下是源码中 Comparable 接口的定义:

package java.lang;

/**
 * 该接口对实现它的每个类的对象强加了总排序。
 * 这种排序称为类的自然排序,而类的compareTo 方法称为其自然比较方法。
 */
public interface Comparable<T> {
    int compareTo(T o);
}

Comparable 接口用于定义对象的自然顺序。若一个类实现了 Comparable 接口,则表明其实例可以相互比较,可以理解为 该类支持排序

Comparable 接口由一个名为 的方法组成 compareTo(),该方法返回一个表示比较结果的整数值。

compareTo() 方法 返回值/比较结果 的的规则:

  • 如果调用对象小于正在比较的对象,则返回负整数。
  • 如果调用对象等于正在比较的对象,则为零。
  • 如果调用对象大于正在比较的对象,则为正整数。

一个类实现了 Comparable 接口,它就支持自然排序了。

  • 可以使用 Arrays.sort()Collections.sort() 方法对保存该对象的集合进行排序。
  • 可以不指定比较器直接保存到有序集合(如:TreeSet, TreeMap)中。

注意:

  1. 强烈建议(尽管不强制要求)自然排序与 equals 保持一致。
  2. null 不是任何类的实例,即使 e.equals(null) 返回 false,e.compareTo(null) 也应该抛出 NullPointerException

下面的例子定义 Person 类并实现 Comparable 接口:

package cn.itmob.demo;

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.age = age;
        this.name = name;
    }
    
    @Override
    public int compareTo(Person o) {
        return this.age - o.getAge();
    }
}

创建保存 Person 实例的集合并排序:

package cn.itmob.demo;

public class SortingExample {
    public static void main(String[] args) {
        Person[] people = {
            new Person("zhang san", 30),
            new Person("li si", 20),
            new Person("wang wu", 40)
        };
        Arrays.sort(people);
        for (Person person : people) {
            System.out.println(person.getName() + ": " + person.getAge());
        }
    }
}

输出排序结果:

li si: 20
zhang san: 30
wang wu: 40

Comparator 接口

如下是源码中 Comparator 接口的定义:

package java.util;

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

// 核心的方法有两个:compare() 和 equals()。
// 从 JDK 1.8 开始新增了多个其他方法对函数式编程进行支持,详见其他文章,这里不过多介绍。

Comparator 接口是比较器接口,提供了一种方法来对未实现 Comparable 接口的对象进行排序,或者为已经实现了 Comparable 接口的对象定义自定义的排序逻辑。Comparator 接口允许您创建单独的比较逻辑,而无需修改原始类。

Comparator 接口定义了一个名为 compare() 的方法,该方法将两个对象作为输入参数并返回一个表示比较结果的整数值。

compare() 方法的返回值/比较结果 的规则与 compareTo() 方法相同:

  • 如果第一个对象小于第二个对象,则返回负整数。
  • 如果第一个对象等于第二个对象,则返回零。
  • 如果第一个对象大于第二个对象,则返回正整数。

下面是 Comparator 接口的实现,根据年龄比较两个 Person 实例:

package cn.itmob.demo;

import java.util.Comparator;

public class NameComparator implements Comparator<Person> {
    @Override
    public int compare(Person person1, Person person2) {
        return person1.getName().compareTo(person2.getName());
    }
}
package cn.itmob.demo;

public class SortingExample {
    public static void main(String[] args) {
        Person zhangSan = new Person("zhang san", 30);
        Person liSi = new Person("li si", 20);
        Person liSi2 = new Person("li si", 40);

        NameComparator comparator = new NameComparator();
        System.out.println(comparator.compare(zhangSan, liSi));
        System.out.println(comparator.compare(liSi, zhangSan));
        System.out.println(comparator.compare(liSi, liSi2));
    }
}

比较结果:

14
-14
0

如何在排序中使用自定义比较器:

package cn.itmob.demo;

public class SortingExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("zhang san", 25),
                new Person("li si", 42),
                new Person("wang wu", 30));

        Collections.sort(people, new NameComparator());

        for (Person person : people) {
            System.out.println(person.getName() + ": " + person.getAge());
        }
    }
}

使用自定义比较器根据名称进行排序的结果:

li si: 42
wang wu: 30
zhang san: 25

如果在排序方法中没有提供比较器,也没有实现 Comparable 接口并重写 compareTo 方法,执行时会出现没有找到任何排序的方法的错误:

SortingExample.java:38: error: no suitable method found for sort(List<Person>)
        Collections.sort(people);
                   ^
method Collections.<T#1>sort(List<T#1>) is not applicable
      (inference variable T#1 has incompatible bounds
        equality constraints: Person
        upper bounds: Comparable<? super T#1>)
    method Collections.<T#2>sort(List<T#2>,Comparator<? super T#2>) is not applicable
      (cannot infer type-variable(s) T#2
        (actual and formal argument lists differ in length))
  where T#1,T#2 are type-variables:
    T#1 extends Comparable<? super T#1> declared in method <T#1>sort(List<T#1>)
    T#2 extends Object declared in method <T#2>sort(List<T#2>,Comparator<? super T#2>)

Comparable 和 Comparator 之间的区别:

  • 一个类实现了 Comparable 接口,意味着该它可以直接进行比较/排序,但比较的方式只有这一种。
  • 一个类无论是否实现了 Comparable 接口,如果需要进行不同方式的比较/排序,则可以自定义一个或多个比较器(实现 Comparator 接口)。

Comparable 和 Comparator 如何选择呢?

使用 Comparable 还是 Comparator 要根据应用程序实际的要求来选择。

以下是一些规则:

  • 如果排序逻辑是要排序的对象固有的并且不会更改,使用 Comparable。
  • 如果需要定义多个排序规则或对未实现 Comparable 的对象进行排序时,使用 Comparator。
  • 如果一个类实现了 Comparable 接口支持自然排序,仍然可以使用 Comparator 进行自定义排序。

总结:

如果需要基于自然顺序来排序,选择 Comparable,如果要按照对象的不同属性进行排序或需要多种排序逻辑,选择 Comparator