在項目開發中,我們經常要對一組數據進行排序,或者升序或者降序,在Java中排序有多種方式,最土的方法就是自己寫排序算法,比如冒泡排序、快速排序、二叉樹排序等,但一般不要自己寫,JDK已經為我們提供了很多的排序算法,我們採用「拿來主義」就成了。
在Java中,要想給數據排序,有兩種實現方式,一種是實現Comparable接口,一種是實現Comparator接口,這兩者有什麼區別呢?我們來看一個身邊的例子,就比如給公司職員排序吧,最經常使用的是按照工號排序,先定義一個職員類代碼,如下所示:
class Employee implements Comparable<Employee>{
//id是根據進入公司的先後順序編碼的
private int id;
//姓名
private String name;
//職位
private Position position;
public Employee(int_id, String_name, Position_position){
id=_id;
name=_name;
position=_position;
}
/*id、name、position的getter/setter方法省略*/
//按照id號排序,也就是資歷的深淺排序
@Override
public int compareTo(Employee o){
return new CompareToBuilder()
.append(id, o.id).toComparison();
}
@Override
public String toString(){
return ToStringBuilder.reflectionToString(this);
}
}
這是一個簡單的JavaBean,描述的是一個員工的基本信息,其中id號是員工編號,按照進入公司的先後順序編碼,position是崗位描述,表示是經理還是普通職員,這是一個枚舉類型,代碼如下:
enum Position{
Boss, Manager, Staff
}
職位有三個級別:Boss(老闆),Manager(經理),Staff(普通職員)。
注意Employee類中的compareTo方法,它是Comparable接口要求必須實現的方法,這裡使用apache的工具類來實現,表明是按照id的自然序列排序的(也就是升序)。一切準備完畢,我們看看如何排序:
public static void main(Stringargs){
List<Employee>list=new ArrayList<Employee>(5);
//一個老闆
list.add(new Employee(1001,\"張三\",Position.Boss));
//兩個經理
list.add(new Employee(1006,\"趙七\",Position.Manager));
list.add(new Employee(1003,\"王五\",Position.Manager));
//兩個職員
list.add(new Employee(1002,\"李四\",Position.Staff));
list.add(new Employee(1005,\"馬六\",Position.Staff));
//按照id排序,也就是按照資歷深淺排序
Collections.sort(list);
for(Employee e:list){
System.out.println(e);
}
}
在收集數據時按照職位高低來收集,這也是「為領導服務」理念的體現嘛,先登記領導,然後是小領導,最後是普通員工。排序後的輸出如下:
Employee@1037c71[id=1001,name=張三,position=Boss]
Employee@b1c5fa[id=1002,name=李四,position=Staff]
Employee@f84386[id=1003,name=王五,position=Manager]
Employee@15d56d5[id=1005,name=馬六,position=Staff]
Employee@efd552[id=1006,name=趙七,position=Manager]
是按照id號升序排列的,結果正確,但是,有時候我們希望按照職位來排序,那怎麼做呢?此時,重構Employee類已經不合適了,Employee已經是一個穩定類,為了一個排序功能修改它不是一個好辦法,那有什麼更好的解決辦法嗎?
有辦法,看Collections.sort方法,它有一個重載方法Collections.sort(List<T>list, Comparator<?super T>c),可以接受一個Comparator實現類,這下就好辦了,代碼如下:
class PositionComparator implements Comparator<Employee>{
@Override
public int compare(Employee o1,Employee o2){
//按照職位降序排列
return o1.getPosition().compareTo(o2.getPosition());
}
}
創建了一個職位排序法,依據職位的高低進行降序排列,然後只要把Collections.sort(list)修改為Collections.sort(list, new PositionComparator())即可實現按職位排序的要求。
現在問題又來了:按職位臨時倒序排列呢?注意只是臨時的,是否要重寫一個排序器?完全不用,有兩個解決辦法:
直接使用Collections.reverse(List<?>list)方法實現倒序排列。
通過Collections.sort(list, Collections.reverseOrder(new PositionComparator()))也可以實現倒序排列。
第二個問題:先按照職位排序,職位相同再按照工號排序,這如何處理?這可是我們經常遇到的實際問題。很好處理,在compareTo或compare方法中先判斷職位是否相等,相等的話再根據工號排序,使用apache的工具類來簡化處理,代碼如下:
public int compareTo(Employee o){
return new CompareToBuilder()
.append(position, o.position)//職位排序
.append(id, o.id).toComparison();//工號排序
}
在JDK中,對Collections.sort方法的解釋是按照自然順序進行升序排列,這種說法其實是不太準確的,sort方法的排序方式並不是一成不變的升序,也可能是倒序,這依賴於compareTo的返回值,我們知道如果compareTo返回負數,表明當前值比對比值小,零表示相等,正數表明當前值比對比值大,比如我們修改一下Employee的compareTo方法,如下所示:
public int compareTo(Employee o){
return new CompareToBuilder()
.append(o.id, id).toComparison();
}
兩個參數調換了一下位置,也就是compareTo的返回值與之前正好相反,再使用Collections.sort進行排序,順序也就相反了,這樣就實現了倒序。
第三個問題:在Java中,為什麼要有兩個排序接口呢?
很多同學都提出了這個問題,其實也好回答,實現了Comparable接口的類表明自身是可比較的,有了比較才能進行排序;而Comparator接口是一個工具類接口,它的名字(比較器)也已經表明了它的作用:用作比較,它與原有類的邏輯沒有關係,只是實現兩個類的比較邏輯,從這方面來說,一個類可以有很多的比較器,只要有業務需求就可以產生比較器,有比較器就可以產生N多種排序,而Comparable接口的排序只能說是實現類的默認排序算法,一個類穩定、成熟後其compareTo方法基本不會改變,也就是說一個類只能有一個固定的、由compareTo方法提供的默認排序算法。
注意 Comparable接口可以作為實現類的默認排序法,Comparator接口則是一個類的擴展排序工具。