Comparator和Comparable接口是如何帮助我们排序的

1.前言

在我们使用数组或者集合的时候,我们经常需要对这个数组或集合进行排序,除了自己定义一个排序的方法,jdk给我们提供的Collections和Arrays工具类都提供了sort()方法供我们排序,并且可以由我们自己定义排序规则,本文着重讨论这个排序规则是如何定义的

在看sort()方法前,我们要先了解下两个接口,分别是Comparator接口

2.Comparator接口

Comparator<T>接口是java.util包下的一个函数式接口(函数式接口即用@FunctionalInterface标注的接口,这类接口支持使用lambda表达式),该接口定义了一个compare方法

int compare(T o1, T o2);

源码上对这个抽象方法的注释大概意思是:当o1小于,等于或大于o2时,返回负数,0或正数。

比如比较Integer类型的数,我们的实现方法就可以这样写

int compare(Integer o1,Integer o2){
    return o1 - o2;
}

 即Comparator接口的compare方法是定义了一个比较规则,他要求o1如果小于,等于或大于o2,需要返回负数,0或正数,我们需要做的就是写出满足这个比较规则的实现代码。

通常我们不止需要比较数字,字符这类简单的数据类型,有时还需要比较我们自己定义的类。比如List里存的是Person类型的数据,这个Person有年龄、姓名和身高的属性,那么我们就需要在compare方法中定义Person的比较规则(比如按年龄大小排序,当年龄相同按身高排序,如果身高也相同就按姓名排序),如下

int compare(Person p1,Person p2){
    //先按照年龄排序
    if(!p1.getAge().equals(p2.getAge())){
        return p1.getAge().compareTo(p2.getAge);
    }
    //如果年龄相等就按照身高排序
    if(!p1.getHeight.equals(p2.getHeight())){
        return p1.getHeight().compareTo(p2.getHeight);
    }
    //...
}

3.Comparable接口

Comparable<T>是java.lang包下的一个接口,里面只有一个需要实现的抽象方法

public int compareTo(T o);

源码对这个抽象方法的注释大概意思是:当该对象小于,等于或大于参数传入的对象时,返回负数,0或正数,这里说的该对象就是调用compareTo方法的对象。

可以发现Comparable接口和Comparator接口都是定义了比较规则,但是Comparable接口定义的是自己和另一个对象的比较规则,而Comparator接口定义的是传入的两个对象的比较规则

4.sort()

知道了这两个接口的定义,我们就来看下sort()方法是如何通过这两个接口实现排序的

首先给出一个例子

    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        list.add("dsad");
        list.add("dbd");
        list.add("dbdk");
        list.add("dsarr");
        list.add("dsad");
        list.add("de");
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);
    }

运行结果如下

 可以发现sort()方法帮我们按照升序的规则进行了排序,我们进入sort()方法看下他做了什么操作

    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }

可以看到他内部调用了我们传入的list对象的sort()方法,传入了一个null参数,进入这个方法看下

    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

可以发现这个参数实际上是我们的Comparator接口,并且我们发现,实际上是把这个集合变为数组,然后调用Arrays.sort()方法进行排序,所以Collections.sort()方法本质还是在调用Arrays.sort()方法。让我们看下Arrays.sort()方法里面做了什么

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

这里可以看到,如果我们之前没有传入我们自己定义的Comparator接口,那么就会调用sort(a)方法,否则就会用我们自己定义的排序规则去排序,这里有几种不同的排序算法,我们这里不去关注他具体用哪种排序算法。先看下sort(a)(即没有定义排序规则的情况)

    private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low,
                                  int high,
                                  int off) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low &&
                         //使用compareTo来比较
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

        //部分代码...
    }

这里展示了部分代码,可以看到,代码中通过了compareTo方法来比较两个数的大小,所以如果我们在最外层调用sort()方法的时候,没有传入比较规则,那么底层就是调用集合中元素的compareTo方法去比较的。

并且注意,这里代码是按照升序来排序的,当dest[j-1]与dest[j]的比较返回是正数的话,说明dest[j-1]比dest[j]要大,此时调用swap调换两个元素的位置,这样最后排序出来的元素就是按照升序排序的。

那如果我不实现Comparable接口,这方法不就"炸"了!

我们重新看下我们最外层调用的sort方法

    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }

可以看到这里对我们的泛型T是作出了限制的,要求一定要继承(实现)Comparable接口,并且Comparable接口里的泛型必须是泛型T的父类(或本身),所以如果不实现Comparable接口,那么程序编译都通过不了,换个角度,这也是为什么必须实现Comparable接口的原因 

5.sort() 传入比较规则

看下传入比较规则sort方法是如何运行的,还是用前面的例子

    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        list.add("dsad");
        list.add("dbd");
        list.add("dbdk");
        list.add("dsarr");
        list.add("dsad");
        list.add("de");
        System.out.println(list);
        Collections.sort(list,(o1, o2) -> o1.length()-o2.length());
        System.out.println(list);
    }

运行结果如下

这里我定义的规则是根据字符串长度来比较大小,长度越长则认为字符串越"大"。我们将目光回到判断是否传入比较规则的代码

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

看下传入比较规则方法

    private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low, int high, int off,
                                  Comparator c) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

        //部分代码...
    }

 可以发现如果传入了比较规则,调用的就是比较规则的compare代码,同样是按照升序排序的逻辑

6.倒序

那怎么实现倒叙排序呢?前面我们看到sort()方法底层排序逻辑就是按照升序来排序的,很明显我们是不能去改变源码的,但是我们可以改变我们比较规则的逻辑,继续用上面的例子

    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        list.add("dsad");
        list.add("dbd");
        list.add("dbdk");
        list.add("dsarr");
        list.add("dsad");
        list.add("de");
        System.out.println(list);
        Collections.sort(list,(o1, o2) -> o2.compareTo(o1));
        System.out.println(list);
    }

可以看到这里我们用o2.compareTo(o1)的比较规则实现了倒序,前面我们说过compare方法的定义是:当o1小于,等于或大于o2时,返回负数,0或正数。然后compareTo方法的定义是:当该对象小于,等于或大于参数传入的对象时,返回负数,0或正数。比如:o2元素比o1要大,此时compareTo返回的是正数,此时compare方法返回值也是正数,所以认为o1比o2要大。如果觉得绕可以自己思考下,这样排序就被"颠倒"过来了,从而实现了我们的倒叙

7.总结

sort方法依赖我们的两个比较接口实现排序,我们通过自己定义比较规则,可以按照自定义的方法来排序相应的元素,并实现倒序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值