Comparable和Comparator

Comparable


几乎对所有对象都通用的方法。
compareTo 方法是 Comparable 接口中唯一的方法。 compareTo 方法不但允许进行简单的等同性比较,而且允许执行顺序比较,除此之外,它与 Objectequals 方法具有相似的特征,它还是个泛型。类实现了 Comparable 接口,就表名它的实例具有内在的排序关系。为实现 Comparable 接口的对象数组进行排序就这么简单:

Arrays.sort(a);

事实上,java平台类库中的所有值类都实现了 Comparable 接口

以上内容来自 Effective java 第二版


Comparable 可以认为是一个内比较器,实现了 Comparable 接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了 Comparable 接口的类如何比较,则依赖 compareTo 方法的实现, compareTo 方法也被称为自然比较方法。如果开发者 add 进入一个 Collection 的对象想要 Collectionssort 方法帮你自动进行排序的话,那么这个对象必须实现 Comparable 接口。 compareTo 方法的返回值是 int ,有三种情况:
1、比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数
2、比较者等于被比较者,那么返回0
3、比较者小于被比较者,那么返回负整数

public class Domain implements Comparable<Domain>
{
    private String str;

    public Domain(String str)
    {
        this.str = str;
    }

    public int compareTo(Domain domain)
    {
        if (this.str.compareTo(domain.str) > 0)
            return 1;
        else if (this.str.compareTo(domain.str) == 0)
            return 0;
        else 
            return -1;
    }

    public String getStr()
    {
        return str;
    }
}
public static void main(String[] args)
    {
        Domain d1 = new Domain("c");
        Domain d2 = new Domain("c");
        Domain d3 = new Domain("b");
        Domain d4 = new Domain("d");
        System.out.println(d1.compareTo(d2));
        System.out.println(d1.compareTo(d3));
        System.out.println(d1.compareTo(d4));
    }

输出结果

0
1
-1

注意

前面说实现 Comparable 接口的类是可以支持和自己比较的,但是其实代码里面 Comparable 的泛型未必就一定要是Domain,将泛型指定为 String 或者指定为其他任何任何类型都可以----只要开发者指定了具体的比较算法就行。

Comparator

Comparator 可以认为是是一个外比较器,个人认为有 两种情况 可以使用实现 Comparator 接口的方式:

  1. 一个对象不支持自己和自己比较(没有实现 Comparable 接口),但是又想对两个对象进行比较
  2. 一个对象实现了 Comparable 接口,但是开发者认为 compareTo 方法中的比较方式并不是自己想要的那种比较方式

要解决这个问题,可以创建一个实现了 Comparator 接口的单独的类,这是策略设计模式的一个应用实例。这个类有 compare()equals() 方法。然而不一定要实现 equals() 方法,除非有特殊的性能需要,因为无论何时创建一个类,都是间接继承 Object ,而 Object 带有 equals() 方法。所以只需要默认的 Objectequals() 方法就可以满足接口的要求了
源码:

public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

    ......

来自 java编程思想/ thinking in java


Comparator 接口里面有一个 compare 方法,方法有两个参数 T o1T o2 ,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和 Comparable 接口一样是 int

有三种情况:

  1. o1大于o2,返回正整数
  2. o1等于o2,返回0
  3. o1小于o3,返回负整数

写个很简单的例子,上面代码的Domain不变(假设这就是第2种场景,我对这个 compareTo 算法实现不满意,要自己写实现):

public class DomainComparator implements Comparator<Domain>
{
    public int compare(Domain domain1, Domain domain2)
    {
        if (domain1.getStr().compareTo(domain2.getStr()) > 0)
            return 1;
        else if (domain1.getStr().compareTo(domain2.getStr()) == 0)
            return 0;
        else 
            return -1;
    }
}
public static void main(String[] args)
{
    Domain d1 = new Domain("c");
    Domain d2 = new Domain("c");
    Domain d3 = new Domain("b");
    Domain d4 = new Domain("d");
    DomainComparator dc = new DomainComparator();
    System.out.println(dc.compare(d1, d2));
    System.out.println(dc.compare(d1, d3));
    System.out.println(dc.compare(d1, d4));
}

输出结果:

0
1
-1

当然因为泛型指定死了,所以实现 Comparator 接口的实现类只能是两个相同的对象(不能一个Domain、一个String)进行比较了,因此实现 Comparator 接口的实现类一般都会以"待比较的实体类+Comparator"来命名

总结

Comparator 比较时,执行流程应该是
对不同对象的比较,应该创建一个 Comparator 比较器对象,传入两个不同的对象,然后调用Comparator的 Compare 方法去进行比较,又因为所有的对象都默认实现了Comparable 接口,所有又会默认调用 CompareTo() 方法去比较

后者相比前者有如下优点:

  1. 如果实现类没有实现 Comparable 接口,又想对两个类进行比较(或者实现类实现了 Comparable 接口,但是对 compareTo 方法内的比较算法不满意),那么可以实现 Comparator 接口,自定义一个比较器,写比较算法
  2. 实现 Comparable 接口的方式比实现 Comparator 接口的耦合性 要强一些,如果要修改比较算法,要修改 Comparable 接口的实现类,而实现 Comparator 的类是在外部进行比较的,不需要对实现类有任何修改。从这个角度说,其实有些不太好,尤其在我们将实现类的 .class 文件打成一个 .jar 文件提供给开发者使用的时候。实际上实现 Comparator 接口的方式后面会写到就是一种典型的策略模式。

当然,这不是鼓励用 Comparator ,意思是开发者还是要在具体场景下选择最合适的那种比较器而已。

使用 Comparator.comparing 进行排序

comparing 方法一

查看 Comparator 类内部实现,还有一个 comparing 方法,实现如下,

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

其返回值是 (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); 一个 lambda 表达式,也就是一个 Compator

comparing 方法二

public static <T, U> Comparator comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}

comparing 方法一不同的是 该方法多了一个参数 keyComparatorkeyComparator 是创建一个自定义的比较器。

Collections.sort(demo, Comparator.comparing(
demo::getName, (o1, o2) -> {
return o2.compareTo(o1);
}));

使用 _Comparator.reversed _进行排序

返回相反的排序规则,

/**
• 相反的排序规则
*/
Collections.sort(employees, Comparator.comparing(Employee::getName).reversed());
employees.forEach(System.out::println);

使用 _Comparator._nullsFirst进行排序

当集合中存在null元素时,可以使用针对null友好的比较器,null元素排在集合的最前面

1. demo.add(null);  //插入一个null元素
2. demo.sort(demo, Comparator.nullsFirst(Comparator.comparing(demo::getName)));
3. demo.forEach(System.out::println);
4. 
5. 
6. Collections.sort(demo, Comparator.nullsLast(Comparator.comparing(demo::getName)));
7. demo.forEach(System.out::println);

参考:

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务