讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議73:使用Comparator進行排序 >

建議73:使用Comparator進行排序

在項目開發中,我們經常要對一組數據進行排序,或者升序或者降序,在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接口則是一個類的擴展排序工具。