开始
Comparator接口或者Comparable接口在日常开发工作中是经常用到的,用于比较一组数据或者对象,在java8之后,也可以看到在Comparator接口中加入了一些default方法和static方法,这里做一个简单说明。
Comparator接口和Comparable接口
这两个接口首先要做一个简单区别。
Comparable接口
1 | * Lists (and arrays) of objects that implement this interface can be sorted |
可以看到注释中说明了实现了该接口的对象,在数组中可以使用Collections.sort或者Arrays.sort方法实现排序,或者实现了该接口的对象可以作为sortedMap或者SortedSet的key。这里也提到我们不用制定一个排序或者作为key的Comparator接口。
1 | public interface Comparable<T> { |
在compareTo方法上的注释中提到,必须确保 x.compareTo(y)
和y.compareTo(x)
的结果是一致的,并且这也意味着当x.compartTo(y)
抛出一个异常,那么y.compareTo(x)
也应该去抛出一个异常,那么这里就思考到了一个关于null的设计:null.compareTo(obj)
我们肯定知道会有NPE,那么你在实现compareTo方法的时候,如果obj.compareTo(null)
这里也应该去抛出NPE。
这里就不去写具体的demo去演示了,这里理解为一个对象实现了Comparable接口,那么这个对象就是可比较的,并且在排序等场景下调用实现接口中的compareTo方法。
Comparator接口
Comparator接口要理解为比较器,实现其接口的类其实是比较器的一种实现,相当于一个比较的函数定义。来看下他的注释:
1 | * A comparison function, which imposes a <i>total ordering</i> on some |
这里我们看到Arrays、Collections也提供了重载的sort方法,支持传入一个集合/数组和Comparator接口的实例。当然当前列表/数组中的对象不一定是实现了Comparable接口。
类实现了comparable接口之后,可以直接调用排序方法;而当使用comparator时,不需要类实现,具体使用时(也就是调用某些方法时)的需要类和该comparator绑定起来来实现。comparable实现内部排序,Comparator是外部排序。
个人感觉Comparator接口更符合解耦的思想,更好维护些。
java8之后的Comparator接口
在java8之后Comparator接口增加了很多default方法和static方法来方便定义比较器。
reserved方法
1 | /** * Returns a comparator that imposes the reverse ordering of this * comparator. * * @return a comparator that imposes the reverse ordering of this * comparator. * @since 1.8 */ |
comparing方法
1 | /** |
comparing方法参数是一个函数式接口keyExtractor,意识即为指定排序对象中的排序键,这里注意排序键这里标注了Comparable接口。
同时我们也可以看到有重载的comparing方法:
1 | /** |
第二个参数也很好理解,提取完sort key之后,要定义关于这个key的Comparator,在注释中的例子也比较好理解。 这里有个小tips:在String类中,提供了一个实现Comparator接口的常量来标识不对语言敏感的字典序排序器。
1 | /** |
在Comparator接口中,也直接提供了具体类型的三个comparing方法:
1 | public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) { |
thenComparing方法
1 | /** |
从方法名称上知道这是当比较相同时的使用的一个排序规则,这里需要注意看具体实现是会先调用比较器实例中的compare方法来进行比较一轮,当结果等于0的时候才会调用other这个比较器规则进行比较。比如下面的代码:
1 | List<String> strings = Arrays.asList("def", "abc", "hel", "world"); |
当然因为有了 comparing方法的支持,所以也就有了下面两个thenComparing的重载方法
1 | default <U extends Comparable<? super U>> Comparator<T> thenComparing( |
1 | default <U> Comparator<T> thenComparing( |
这里也不赘述提供的thenComparingInt、thenComparingDouble这类的方法。
null 友好的比较器
看到Comparator接口中有两个对null友好的比较器方法:
1 | /** |
这里是通过Comparators这个工厂类提供的NullComparator比较器实现的,看到注释有一条需要注意是如果不指定comparator参数,即传入null,那么所有的非null参数都会被视为相等。
集合中插入null记录的场景也不是很常见,知道有这么个null友好的Comparator即可。
彩蛋
这里遇到了一个使用比较器 类型推断导致编译不过的问题:
1 | // 这里尝试使用lambda方式去实现根据字符串长度降序排序 使用方法引用不会有这问题 因为指定了String::length |