Java中提供了两种对集合或数组中元素进行排序的方法,一种是实现Comparable接口,另一种是实现Comparator接口。
虽然这两种接口看起来差不多,但是在使用方法上有比较大的差别,下面我将开始介绍这两种接口以及它们的一些应用场景。
接口名称 | 包 | 主要函数 |
---|---|---|
Comparable | java.lang | int compareTo(T o)(比较此对象与指定对象的顺序) |
Comparator | java.util | int compare(T o1,T o2)(比较用来排序的两个参数) ,boolean equals(Object obj)(指示某个其他对象是否“等于”此Comparator) |
一、Comparable接口
public interface Comparable<T>
下面这段介绍来自官方文档:
此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。
实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序。实现此接口的可以用作有序映射(实现了SortedMap接口的对象)或有序集合(实现了SortedSet接口的对象)中的元素,无需指定比较器。
建议(虽然不是必需的)最好使自然排序与equals一致。
所谓自然排序与equals一致指的是 类A 对于每一个 o1 和 o2 来说,当且仅当 ( o1.compareTo( o2 ) )与 o1.equals( o2 )具有相同的 布尔值 时,类A的自然排序才叫做与equals一致。
下面将文档中出现的几个点抽出来单独理解:
- 自然排序:Comparable接口强行对实现它的类的对象进行整体排序,这样的排序称为该类的自然排序;
- 自然排序与equals一致:类A 对于每一个 o1 和 o2 来说,当且仅当 ( o1.compareTo( o2 ) )与 o1.equals( o2 )具有相同的 布尔值 时,类A的自然排序才叫做与equals一致。
方法详细信息:
int compareTo(T o) 比较此对象与指定对象的顺序
比较此对象与指定对象的顺序,如果该对象小于、等于、或大于指定对象,则分别返回负整数、零或正整数。
实现类必须确保对于所有的 x 和 y 都存在 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 的关系。(这意味着如果 y.compareTo(x) 抛出一个异常,则 x.compareTo(y) 也要抛出一个异常。)
- 参数: o - 要比较的对象
- 返回: 负整数、零或正整数,根据此对象是小于、等于还是大于指定对象
- 抛出: CalssCastException - 如果指定对象的类型不允许它与此对象进行比较
通过一个小例子来介绍如何使用Comparable接口:
下面是一个普通的Person类,name和age两个属性以及对应的get和set方法。
下面是一个PersonTest测试类以及打印的结果:
我们往一个list当中添加了五个Person对象,然后将此list中的内容打印输出,发现输出的顺序和添加的顺序是一致的。
如果我们此时需要按照Person对象的年龄从小到大输出,该如何实现呢?
这个时候Comparable接口就派上用场了
我们把Person改造一下,让它实现Comparable<T>接口并重写其compareTo方法:
然后在测试类中调用 Collections.sort() 方法对list进行排序之后输出结果:
我们可以注意,其实我们在这里显式地指定了比较器:Collection.sort()。
但是对于那些实现了 SortedMap接口 的对象或者是实现了 SortedSet接口 的对象来说,无需指定比较器即可完成我们所需的功能。
二、Comparator接口
public interface Comparator<T>
下面这段介绍来自官方文档:
强行对某个对象collection进行整体排序的比较函数。可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用 Comparator 来控制某些数据结构(如有序 set 或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
当且仅当对于一组元素 S 中的每个 e1 和 e2 而言,c.compare(e1, e2)==0 与 e1.equals(e2) 具有相等的布尔值时,Comparator c 强行对 S 进行的排序才叫做与 equals 一致 的排序。
当使用具有与 equals 不一致的强行排序能力的 Comparator 对有序 set(或有序映射)进行排序时,应该小心谨慎。假定一个带显式 Comparator c 的有序 set(或有序映射)与从 set S 中抽取出来的元素(或键)一起使用。如果 c 强行对 S 进行的排序是与 equals 不一致的,那么有序 set(或有序映射)将是行为“怪异的”。尤其是有序 set(或有序映射)将违背根据 equals 所定义的 set(或映射)的常规协定。
例如,假定使用 Comparator c 将满足 (a.equals(b) && c.compare(a, b) != 0) 的两个元素 a 和 b 添加到一个空 TreeSet 中,则第二个 add 操作将返回 true(树 set 的大小将会增加),因为从树 set 的角度来看,a 和 b 是不相等的,即使这与 Set.add 方法的规范相反。
注:通常来说,让Comparator也实现 java.io.Serializable 是一个好主意,因为它们在可序列化的数据结构(像 TreeSet 、TreeMap)中可用作排序方法。为了成功地序列化数据结构,Comparator(如果已提供)必须实现Serializable。
方法详细信息:
int compare(T o1, T o2) 比较用来排序的两个参数
比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
boolean equals(Object obj) 指示某个其他对象是否“等于”此Comparator
指示某个其他对象是否“等于”此 Comparator。此方法必须遵守 Object.equals(Object)的常规协定。此外,仅当指定的对象也是一个Comparator,并且强行实施与此 Comparator 相同的排序时,此方法才返回true。因此,comp1.equals(comp2)意味着对于每个对象引用o1和o2而言,都存在sgn(comp1.compare(o1, o2))==sgn(comp2.compare(o1, o2))。注意,不重写Object.equals(Object)方法总是安全的。然而,在某些情况下,重写此方法可以允许程序确定两个不同的 Comparator 是否强行实施了相同的排序,从而提高性能。
下面继续使用Person类来说说该如何通过使用Comparator<T>接口来帮助我们实现排序:
Person类使用的还是上面那个没有实现任何接口的Person类。
那么我们该如何对Person类的对象进行排序呢?
****其实只要合理使用Comparator接口就行了,如下图:**
现在我们可以来回答下面这个问题了:
TreeSet和TreeMap在排序时如何比较元素?Collections工具类中的sort( )方法如何比较元素?
TreeSet要求存放的对象所属类必需实现Comparable接口,该接口提供了比较元素的 compareTo( ) 方法,当插入元素的时候会回调该方法比较元素的大小。
TreeMap要求存放的键值对映射的键必需实现Comparable接口从而根据键对元素进行排序。
另外Collections工具类中的 sort( ) 方法有两种重载形式:
- 第一种要求传入的待排序容器中存放的对象比较实现 Comaprable 接口以实现元素的比较;
- 第二种不强制的要求容器中的元素必须可以进行比较,但是要求传入的第二个参数,参数是 Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于临时定义一个排序规则,通过接口注入比较元素的大小,也就是对回调模式的应用。
附加问题:如果对ArrayList进行排序?
其实我在上面的程序当中已经演示得很清楚了。ArrayList不像TreeMap和TreeSet,可以在元素插入的过程中进行排序。我们可以在元素全部插入完毕时候通过调用Collections工具类的 sort( ) 方法对里面的元素进行排序。