我們應該如何對事物進行比較和排序?這問題聽上去有點莫名其妙,但我希望你認真考慮一下。比方說,我們有一組蘋果:
例1
我們要怎樣對它們進行排序呢?我們希望通過重量進行排序嗎?如果是的話,排序是從最輕到最重還是從最重到最輕?當我們對它們進行排序的時候,我們需要反覆比較兩個蘋果的重量,直到正確地排好所有的蘋果。蘋果1比蘋果2重?那它比蘋果3重嗎?我們需要不斷比較,直到完成排序。Comparable接口可以幫助我們實現這一目標。Comparable本身不能對對象進行排序,但接口定義的方法 int compareTo(T)可以。
compareTo(T)如何工作讓我們通過使用compareTo()方法來看看哪個蘋果更重,開始吧。
例2
compareTo()方法的工作原理是返回一個int值——或正,或負,或為零。它通過調用作為參數的對象來比較對象。負數表示調用的對象比參數「輕」。如果我們用大小來比較蘋果,那麼上面的調用會返回一個負數,例如-400,因為紅蘋果比青蘋果小。如果兩個蘋果重量相等,那麼調用將返回0。如果紅蘋果更重,那麼compareTo()將返回一個正數,例如68。
compareTo()的靈活性如果我們反覆調用上面的compareTo()方法,那麼我們可以通過大小來排序,這很棒,但並非故事的結束。如果我們想通過顏色來給蘋果排序呢?抑或是重量?我們也可以做到。關鍵是,我們的客戶——讓我們叫他胖子農夫(見例3),需要在我們開始開發之前精確定義需要如何對蘋果進行排序。
例3
他可以通過回答這兩個問題來做到這一點:
他希望蘋果如何進行排序?他希望我們比較什麼特徵?
在那樣的環境中,「小於」,「等於」和「大於」指的是什麼意思?
也可以使用多個特徵,這個後面我們會講。
例1:通過重量排序蘋果在第一個例子中,我們將通過重量對蘋果排序。只需要一行代碼。
Collections.sort(apples);
例4
上面的代碼行可以為我們做到所有的排序工作,只要我們事先定義好如何對蘋果進行排序(這就需要多行代碼了)。
讓我們開始寫蘋果類吧。
public class Apple implements Comparable { private String variety; private Color color; private int weight; @Override public int compareTo(Apple other) { if (this.weight < other.weight) { return -1; } if (this.weight == other.weight) { return 0; } return 1; }}
例5
這是Apple類的第一個版本。由於我們使用的是compareTo方法,並且正在排序蘋果,所以我實現了Comparable接口。在這第一個版本中,我們通過重量比較對象。在我們的compareTo()方法中,我們寫一個if條件,說明如果這個蘋果的重量小於其他的蘋果,那麼返回一個負數,為了保持簡單,我們假定它為-1。請記住,這意味著這個蘋果輕於Apple 『other』。在第二個if語句中,我們要說明,如果蘋果重量相等,那麼返回一個0。當然,如果這個蘋果既不是更輕,又不是一樣重,那就只能比其他蘋果更重了。在這種情況下,我們返回一個正數,假定為1。
例2:通過多個特徵排序蘋果正如我前面提到的,我們還可以使用compareTo()比較多個特徵。比方說,我們第一通過品種排序蘋果,但如果兩個蘋果是同一品種,那麼我們就按顏色排序。最後,如果這兩個特性相同,那麼我們將按重量排序。雖然我們可以手動實現這件事,就像我在最後一個例子中做的那樣,但是其實可以用一種簡潔得多的方式實現。一般來說,最好是重用現有的代碼,而不是自己寫。我們可以在Integer、String和枚舉類中使用compareTo方法來比較值。由於我們沒有使用Integer對象,用了int,所以我們不得不使用來自於Integer包裝器類的一個靜態的helper方法來比較兩個值。
public class Apple implements Comparable { private String variety; private Color color; private int weight; @Override public int compareTo(Apple other) { int result = this.variety.compareTo(other.variety); if (result != 0) { return result; } if (result == 0) { result = this.color.compareTo(other.color); } if (result != 0) { return result; } if (result == 0) { result = Integer.compare(this.weight, other.weight); } return result; }}
例6
在例6中,我們比較了客戶指定的蘋果的第一特性,它們的品種。如果compareTo()調用的結果為非零,那麼我們返回值。否則,我們調用另一個compareTo()直到得到一個非零值,或者直到已經比較完這三個特徵。儘管此代碼可以工作,但它不是最有效或乾淨的解決方案。在例3中,我們重構我們的代碼,使其更簡單。
@Overridepublic int compareTo(Apple other) { int result = this.variety.compareTo(other.variety); if (result == 0) { result = this.color.compareTo(other.color); } if (result == 0) { result = Integer.compare(this.weight, other.weight); } return result;}
例7
正如你所看到的,這大大減少了代碼,並且每一次比較只要一行代碼。如果一個compareTo()調用的結果是零,那麼我們就轉移到下一個相同if語句的比較中。順便說一句,這是成為Clean Coder的一個很好的例子。通常情況下,你不需要立即寫出乾淨的代碼;你可以從一個粗略的想法開始,使其可以工作,然後不斷改進,直到你儘可能得讓它乾淨就可以了。
Comparable,hashCode以及Equals你可能會注意到compareTo()看起來有點像hashCode()和equals()方法。但是,它們有一個重要的區別。對於hashCode()和equals()方法,比較個體屬性的順序不影響返回的值,但是,在compareTo()中,通過你比較對象的順序來定義對象的順序。
結論在結論中我只想強調Comparable接口是多麼的重要。它既用於java.util.Arrays,也用於java.util.Collections實用程序類,來排序元素和搜索排序集合中的元素。使用TreeSet和Tree Map,就更簡單了——想要它們會自動排序必須實現Comparable接口的元素。