由Arrays.sort()简谈Comparable和Comparator

2,608 阅读4分钟

问题引入

  当我们想对一个基本数据类型的数组(如int[ ])进行排序时,直接调用Arrays.sort()方法即可,非常方便,那么当我们想要排序的数组是一个对象数组呢?这个时候就不能调用Arrays.sort()了。Java核心技术卷1告诉我们,如果对象所属的类实现了Comparable接口,就可以调用Arrays.sort()进行排序了。那我们接下来就由Arrays.sort()入手看看Comparable接口以及和它长的很像的Comparator接口有啥关系。

Arrays.sort()

  可以在IDEA里点进去Arrays.sort()的源码进行查看,可以发现这个方法被重载了十几次,参数列表有下面3种:

捕获.PNG
如上图,接收基本数据类型的数组,还有接收float,double,short什么的

1.PNG
如上图,接收一个Object类型的数组,由于Object是一切类的父类,其实就是接收对象数组,好像看到我们想看到的了。。。

2.PNG
还有这种,接收一个泛型数组以及一个Comparator接口实例。

  我们可以继续看源码

3.PNG

4.PNG
代码具体细节先不用管,可以看出,这里其实是一个比较大小进行排序的操作,只不过比较的操作由基本数据类型的dest[j-1]>dest[j];变为了c.compare(dest[j-1], dest[j])>0;(Comparable) dest[j-1]).compareTo(dest[j])>0;罢了,这里比较大小的方法由我们自己定义,Arrays.sort()调用我们定义的方法进行排序。所以理论上讲,我们可以定义个错误的比较方法,从而得到乱七八糟的排序数组。。。

Comparable接口

  如果对象所属的类实现了Comparable接口,我们可以直接对此对象数组调用Arrays.sort()进行排序,对应我们上面的第二种参数列表,方法内部会调用我们自定义的比较代码进行排序。由上面的源码我们也可以看出,比较时会将dest[j-1]先转为Comparable类型实例,所以必须实现Comparable接口。

class Test implements Comparable<Test>{
    private int num;
    public Test(int num) {
        this.num = num;
    }
    @Override
    public int compareTo(Test o){
        return Integer.compare(num,o.num);
    }
    @Override
    public String toString(){
        return "num:"+num;
    }
}
public class someInterface {
    public static void main(String[] args) throws CloneNotSupportedException{
        Test[] temp = new Test[]{new Test(4),new Test(8),new Test(1),new Test(-7)};
        Arrays.sort(temp);
        System.out.println(Arrays.toString(temp));
    }
}

  简单写一个测试程序,定义了一个Test类,实现了Comparable接口,重写了接口里的CompareTo方法,输出如下:

5.PNG

public int compareTo(Test o){
    return 1;
}

  我们也可以瞎写比较方法如上,得到结果如下:

6.PNG
可以看出,调用我们自己定义的排序方法,如果写的不对,结果是比较搞笑的。。。
Comparable接口是在一个类的内部实现,需要在类内部重写CompareTo方法。

Comparator接口

Arrays.sort()还有一个版本需要传入Comparator接口的实例。和Comparable接口不同的是,Comparator接口的实现在类的外部,不需要在内部写什么代码。

class Test{
    private int num;
    public Test(int num) {
        this.num = num;
    }
    public int getNum() {
        return num;
    }
    @Override
    public String toString(){
        return "num:"+num;
    }
}
class TestComparator implements Comparator<Test>{
    @Override
    public int compare(Test o1,Test o2){
        return o1.getNum()-o2.getNum();
    }
}
public class someInterface {
    public static void main(String[] args) throws CloneNotSupportedException{
        Test[] temp = new Test[]{new Test(4),new Test(8),new Test(1),new Test(-7)};
        Arrays.sort(temp,new TestComparator());
        System.out.println(Arrays.toString(temp));
    }
}

  如代码,我们在类外面定义了一个比较器,调用Arrays.sort()时,传入一个比较器对象,即可完成排序。

7.PNG
当然,由于Compartor接口里只有一个抽象方法,我们可以用lambda表达式来简化代码,不需要专门定义一个比较器类了。Arrays.sort(temp,(x,y)->x.getNum()-y.getNum());用这样一行代码就可以完成一样的事情。。。

总结

  对于这兄弟俩,可以总结它们的区别如下:

  1. 处在的位置不同,Comparable在java.lang包里,而Comparator处于java.util包里
  2. 一个要在类内部实现比较方法,一个在外部实现比较方法。倘若我们想保持类的原貌,不在类里加一些其它无关代码,可以选用Comparator接口
  3. 适用性不同,倘若我们需要对一个对象数组以多种方式排序(如按id,按薪资等等),这时候,Comparable接口就无法胜任了,总不能让一个类用两种方式实现Comparable接口吧,但是我们还是可以通过传入多个Comparator实例来实现