[Java] Comparator接口/compare方法的介绍与使用

上一篇文章讲了Comparable接口的使用,建议搭配食用。

背景

在实现Comparable接口的前提下,对象间已经有一套可适用的大小比较规则/排序规则了。然而某些情况下,由compareTo定义的排序规则不再适用,需要一套新的规则,我们该怎么做呢?

修改源码中的compareTo方法或许是可行的,然而这样的弊端也颇多:

  • 不方便,每次更换排序规则都需要找到定义处去修改。
  • 若同一个代码块中同时要运用两种排序规则,修改源码只能适用于其中一种。
  • String类和包装类默认重写了compareTo方法,若需要更改他们的比较规则,修改源码需要直接修改JDK本身。

可见,改源码不见得是一个好的选择。而JDK为了解决这个问题提供了更具普适性的方案:Comparator接口。

介绍

这个长得非常像Comparable的和它一样,也是个带泛型的接口。
compare方法和compareTo方法长得也很像,两者主要区别是前者含两个形参,后者仅有一个。
两者主要在应用方式上有较大的区别,下面将通过举例来详细叙述这一部分。

使用举例

我们知道字符串String默认的排序顺序是由小到大进行排序,那当我们需要其由大到小进行排序,应该怎么做?

	@Test
    public void test3(){
        String[] arr = new String[]{"AA","BB","ZZ","DD","CC"};
        Arrays.sort(arr, new Comparator<String>() {
            // 按照字符串从大到小的顺序排列
            @Override
            public int compare(String o1, String o2) {
                return -o1.compareTo(o2);
            }
        });// 这是个匿名类
        System.out.println(Arrays.toString(arr));
        // [ZZ, DD, CC, BB, AA]
    }

仔细观察上端代码,不难发现其与对String直接排序的区别在于Array.sort方法新增了一个参数,该位置要求传入一个Comparator接口的实现对象。这里通常为简洁起见会直接使用一个匿名类充当。

定义匿名类后重点在于需要重写其compare方法,重写规则与compareTo方法相同,仅仅是形参变成两个。在数组排序中,o1是指前一个元素,o2是指后一个元素。

重写规则:o1大于o2时,返回正整数;反之返回负整数,相等返回0。与compareTo的重写规则相同。

总结来说,使用Comparator接口分为以下几个步骤:

  1. 排序时使用sort两个参数的重载方法
  2. 第二个参数位置通常定义一个Comparator的匿名类
  3. 与compareTo相同的规则去重写匿名类的compare方法,使其按照需要的比较关系返回相应的数值

自定义类举例

清楚基本的使用方法后,我们再看看其在自定义类中的使用。
首先我们自定义了这么个类:

public class Product implements Comparable {
    private String name;
    private double price;
    
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
    ...

该类重写了compareTo方法,按产品价格从低到高进行排序。具体如下:

    @Override
    public int compareTo(Product o) {
        if (this.price > product.price)
        	return 1;
        else if (this.price < product.price)
        	return -1;
        else 
        	return 0;
    }	// 这种写法是按价格从低到高进行排序

重写了compareTo方法后通过sort我们可以直接对Product的数组按价格升序进行排序。而倘若这时候我们需要按名称进行排序,同时若名称相同则按价格进行二级排序呢?

    @Test
    public void test4(){
        Product[] arr = new Product[4];
        arr[0] = new Product("A",59);
        arr[1] = new Product("B",12);
        arr[2] = new Product("C",44);
        arr[3] = new Product("D",102);

        Arrays.sort(arr, new Comparator<Product>() {
            @Override
            public int compare(Product o1, Product o2) {
                if (o1.getName().equals(o2.getName())){
                    return -Double.compare(o1.getPrice(),o2.getPrice());
                }else {
                    return o1.getName().compareTo(o2.getName());
                }
            }
        });   // 未实现Comparable接口会抛异常

        System.out.println(Arrays.toString(arr));
    }

其实与上文提到的String类操作大同小异,无非是重写函数的内部稍微复杂了些。

总结

基于上述内容,我们可以来个小的总结。

  • 实现Comparable接口重写compareTo方法指定的排序规则更为适用,应当用于能应付较多的一般性情景的排序规则,因此又被成为“自然排序”。
  • 通过Comparator接口进行比较的情况下更适用于已经实现了Comparable接口,但需要临时调整一次新的排序规则的情形下,因此又被称为“定制排序”

你可能感兴趣的:(Java)