Java中Comparable与Comparator的区别

292 阅读3分钟

Java 中的 Comparable 和 Comparator 都是比较有用的集合排序接口,但是这俩接口使用却有着明显区别,具体使用哪一个接口,今天我们来一起了解下。

Comparable 接口

Comparable 是一个排序接口,位于 java.lang 包下面,实现该接口的类就可以进行自然排序。先看下 Comparable 接口的定义:

package java.lang;

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

实现 Comparable 接口时,需要实现 compareTo 方法,其中参数 T 为我们要比较的目标对象,compareTo 方法返回值决定了我们的排序顺序:

  • 返回负值(-1 或更小):当前对象排目标对象前面,即正序排列
  • 返回 0:当前对象和目标对象相等,排序不分先后
  • 返回正值(1 或 更大):当前对象排目标对象后面,即倒序排列

下面来举个例子,首先我们定义一个 User 对象:

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class User implements Comparable<User> {
    private String username; // 用户名
    private Integer age; // 年龄

    @Override
    public int compareTo(User nextUser) {
        // 这里我们先直接使用 Integer 自带的 compareTo 进行比较
        return this.age.compareTo(nextUser.getAge());
    }
}

我们来测试下:

public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(User.builder().age(11).username("张三").build());
        userList.add(User.builder().age(9).username("李四").build());
        Collections.sort(userList);
        log.info("userList: {}", userList); 
        // userList: [User(username=李四, age=9), User(username=张三, age=11)]
    }

在 User 对象我们实现了 compareTo 方法,使用 Integer 自带的 compareTo 进行比较,我们看下 Integer 的 compareTo 是怎么实现的:

public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

可以看到,当 x 比 y 小时,返回 -1,即 x 排在 y 前面(根据我们的返回值规则,返回 -1 时:当前对象排目标对象前面);当 x > y 时,返回 1,即 x 排后面,总结下就是 小的值排前大的值排后,所以 Integer 自带的 compareTo 其实就是数值按照从小到大排列,即正排序。

所以 this.age.compareTo(nextUser.getAge()) 其实就是根据 User 的 age 大小进行正排序,小的排前面大的排后面。那么假设有一天我们不想根据 age 排序了,我们想换成根据 username 自然排序,怎么办?如果一股脑调整 User 的 compareTo 逻辑怕是不能应对需求的变动吧,这时候就要用到 Java 中的比较器 Comparator 了。

注意:在实现 Java Comparable 接口时,Java 要求实现必须遵循以下传递比较特性:如果对象 A 大于对象 B,对象 B 大于 对象 C,那么对象 A 也一定是大于对象 C 的。

Comparator 接口

Comparator 又被称为 Java 中的比较器,该接口位于 java.util 包下,定义如下:

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

Comparator 接口标注 @FunctionalInterface 代表其是一个函数式接口,与 Comparable 不同,Comparator 可以选择性地允许比较空参数,同时保持传递比较特性的要求。

  • compare 方法传入 2 个待比较的对象 o1、o2,假设返回值为负值(-1 或更小)代表 o1 排 o2 前面,反之 o1 排 o2 后面
  • equals 方法传入一个 Object 对象,用于判断待比较的 2 个对象是否是一个对象,该接口不用实现,因为 Object 顶级类已经默认实现了

接下来我们使用 Comparator 实现根据 age 从小到大排序的功能:

Collections.sort(userList, (o1, o2) -> {
    return o1.getAge().compareTo(o2.getAge());
});
log.info("userList: {}", userList);
// userList: [User(username=李四, age=9), User(username=张三, age=11)]

同样的,我们想根据 username 排序的话:

Collections.sort(userList, (o1, o2) -> {
    return o1.getUsername().compareTo(o2.getUsername());
});
log.info("userList: {}", userList);
// [User(username=张三, age=11), User(username=李四, age=9)]

可能大家会疑惑为啥 “张三” 还排在 “李四” 前面了,因为汉字 compareTo 时,默认是基于字符串中每个字符的 Unicode 值进行比较的,然后按照字典排序进行排列,字典排序靠前的自然排前面了。

总结

Comparable 和 Comparator 都能实现类排序,但是区别较大,主要有以下几方面:

  • Comparable 是类的内部比较器,而 Comparator 是外部比较器;Comparable 比较只能有一种实现,但是 Comparator 可以根据具体业务要求变换多种比较规则,比较灵活
  • Comparable 的排序规则只适用于该类的对象,而 Comparator 的排序规则可以适用于不同类型的对象
  • 实现 Comparable 接口的类具有默认排序规则,不用使用时再去定义

日常使用的话,推荐自定义 Comparator 的方式。

本文由mdnice多平台发布