可比還是可比?為您需要的排序算法選擇正確的接口
程序員經常需要將數據庫中的元素排序到集合,數組或映射中。在Java中,我們可以實現任何類型的排序算法。使用Comparable接口和compareTo()方法,我們可以使用字母順序,String長度,反向字母順序或數字進行排序。該Comparator界面允許我們以更靈活的方式執行相同操作。
無論我們想做什麼,我們只需要知道如何為給定的接口和類型實現正確的排序邏輯即可。
獲取源代碼
獲取此Java Challenger 的代碼(獲取地址:https://github.com/rafadelnero/javaworld-challengers)。在遵循示例的同時,您可以運行自己的測試。
用自定義對象對Java列表進行排序
對於我們的示例,我們將使用到目前為止其他Java Challengers所使用的POJO。在第一個示例中,我們使用通用類型在類中實現Comparable接口:SimpsonSimpson
[ 在這個由12部分組成的綜合課程中,從入門概念到高級設計模式學習Java!]
<code> class Simpson implements Comparable { String name; Simpson(String name) { this.name = name; } @Override public int compareTo(Simpson simpson) { return this.name.compareTo(simpson.name); } } public class SimpsonSorting { public static void main(String... sortingWithList) { List simpsons = new ArrayList<>(); simpsons.add(new SimpsonCharacter("Homer ")); simpsons.add(new SimpsonCharacter("Marge ")); simpsons.add(new SimpsonCharacter("Bart ")); simpsons.add(new SimpsonCharacter("Lisa ")); Collections.sort(simpsons); simpsons.stream().map(s -> s.name).forEach(System.out::print); Collections.reverse(simpsons); simpsons.stream().forEach(System.out::print); } }/<code>
請注意,我們已經覆蓋了compareTo()方法並傳遞了另一個Simpson對象。我們也重寫了該toString()方法,只是為了使示例易於閱讀。
該toString方法顯示來自對象的所有信息。當我們打印對象時,輸出將是中實現的toString()。
compareTo()方法
該compareTo()方法將給定對象或當前實例與指定對象進行比較,以確定對象的順序。快速瀏覽一下compareTo()工作原理:
我們只能使用與該sort()方法相當的類。如果我們嘗試傳遞Simpson未實現的Comparable,則會收到編譯錯誤。
該sort()方法通過傳遞任何對象來使用多態Comparable。然後將按預期對對象進行排序。
先前代碼的輸出為:
<code> Bart Homer Lisa Marge /<code>
如果我們想顛倒順序,我們可以交換sort()的reverse(); 從:
<code> Collections.sort(simpsons);/<code>
至:
<code> Collections.reverse(simpsons);/<code>
部署該reverse()方法會將先前的輸出更改為:
<code> Marge Lisa Homer Bart /<code>
排序Java數組
在Java中,我們可以對數組進行排序,只要它實現Comparable接口即可。這是一個例子:
<code> public class ArraySorting { public static void main(String... moeTavern) { int[] moesPints = new int[] {9, 8, 7, 6, 1}; Arrays.sort(moesPints); Arrays.stream(moesPints).forEach(System.out::print); Simpson[] simpsons = new Simpson[]{new Simpson("Lisa"), new Simpson("Homer")}; Arrays.sort(simpsons); Arrays.stream(simpsons).forEach(System.out::println); } }/<code>
在第一次sort()調用中,數組被排序為:
<code> 1 6 7 8 9/<code>
在第二個sort()調用中,它被排序為:
<code> Homer Lisa/<code>
請記住,自定義對象必須實現Comparable才能排序,即使是數組也是如此。
我可以對沒有可比對象的對象進行排序嗎?
如果Simpson對象未實現Comparable,則將引發ClassCastException。如果將其作為測試運行,您將看到類似以下輸出的內容:
<code> Error:(16, 20) java: no suitable method found for sort(java.util.List) method java.util.Collections.sort(java.util.List) is not applicable (inference variable T has incompatible bounds equality constraints: com.javaworld.javachallengers.sortingcomparable.Simpson lower bounds: java.lang.Comparable super T>) method java.util.Collections.sort(java.util.List,java.util.Comparator super T>) is not applicable (cannot infer type-variable(s) T (actual and formal argument lists differ in length))/<code>
該日誌可能令人困惑,但請不要擔心。請記住,ClassCastException任何未實現該Comparable接口的已排序對象都將拋出a 。
使用TreeMap對地圖排序
Java API包括許多有助於排序的類,包括TreeMap。在下面的示例中,我們用於TreeMap將鍵排序為Map。
<code> public class TreeMapExample { public static void main(String... barney) { Map simpsonsCharacters = new TreeMap<>(); simpsonsCharacters.put(new SimpsonCharacter("Moe"), "shotgun"); simpsonsCharacters.put(new SimpsonCharacter("Lenny"), "Carl"); simpsonsCharacters.put(new SimpsonCharacter("Homer"), "television"); simpsonsCharacters.put(new SimpsonCharacter("Barney"), "beer"); System.out.println(simpsonsCharacters); } }/<code>
TreeMap使用接口compareTo()實現的方法Comparable。結果中的每個元素Map均按其鍵排序。在這種情況下,輸出為:
<code> Barney=beer, Homer=television, Lenny=Carl, Moe=shotgun/<code>
但是請記住:如果對象未實現Comparable,ClassCastException則將拋出a。
使用TreeSet對集合進行排序
該Set接口負責存儲唯一值,但是當我們使用TreeSet實現時,插入的元素將在我們添加它們時自動排序:
<code> public class TreeSetExample { public static void main(String... barney) { Set simpsonsCharacters = new TreeSet<>(); simpsonsCharacters.add(new SimpsonCharacter("Moe")); simpsonsCharacters.add(new SimpsonCharacter("Lenny")); simpsonsCharacters.add(new SimpsonCharacter("Homer")); simpsonsCharacters.add(new SimpsonCharacter("Barney")); System.out.println(simpsonsCharacters); } }/<code>
此代碼的輸出是:
<code> Barney, Homer, Lenny, Moe/<code>
同樣,如果我們使用的不是Comparable,ClassCastException則將拋出a。
用比較器排序
如果我們不想使用compareTo()POJO類中的相同方法怎麼辦?我們可以覆蓋該Comparable方法以使用其他邏輯嗎?下面是一個示例:
<code> public class BadExampleOfComparable { public static void main(String... args) { List characters = new ArrayList<>(); SimpsonCharacter homer = new SimpsonCharacter("Homer") { @Override public int compareTo(SimpsonCharacter simpson) { return this.name.length() - (simpson.name.length()); } }; SimpsonCharacter moe = new SimpsonCharacter("Moe") { @Override public int compareTo(SimpsonCharacter simpson) { return this.name.length() - (simpson.name.length()); } }; characters.add(homer); characters.add(moe); Collections.sort(characters); System.out.println(characters); } }/<code>
如您所見,此代碼很複雜,並且包含很多重複。compareTo()對於相同的邏輯,我們不得不重寫該方法兩次。如果還有更多元素,我們將不得不為每個對象複製邏輯。
幸運的是,我們具有Comparator接口,該接口使我們可以將compareTo()邏輯與Java類分離。考慮上面使用重寫的同一示例Comparator:
<code> public class GoodExampleOfComparator { public static void main(String... args) { List characters = new ArrayList<>(); SimpsonCharacter homer = new SimpsonCharacter("Homer"); SimpsonCharacter moe = new SimpsonCharacter("Moe"); characters.add(homer); characters.add(moe); Collections.sort(characters, (Comparator. comparingInt(character1 -> character1.name.length()) .thenComparingInt(character2 -> character2.name.length()))); System.out.println(characters); } }/<code>
這些示例說明了Comparable和之間的主要區別Comparator。
使用Comparable時,有你的對象單一,默認的比較。使用Comparator時,你需要要解決現有的compareTo(),或者當你需要使用特定的邏輯更靈活的方式。Comparator將排序邏輯與對象分離,並將邏輯包含compareTo()在sort()方法中。
將Comparator與匿名內部類一起使用
在下一個示例中,我們使用匿名內部類比較對象的值。一個匿名內部類,在這種情況下,任何類,它實現Comparator。使用它意味著我們不必實例化實現接口的命名類。相反,我們compareTo()在匿名內部類內部實現該方法。
<code> public class MarvelComparator { public static void main(String... comparator) { List marvelHeroes = new ArrayList<>(); marvelHeroes.add("SpiderMan "); marvelHeroes.add("Wolverine "); marvelHeroes.add("Xavier "); marvelHeroes.add("Cyclops "); Collections.sort(marvelHeroes, new Comparator() { @Override public int compare(String hero1, String hero2) { return hero1.compareTo(hero2); } }); Collections.sort(marvelHeroes, (m1, m2) -> m1.compareTo(m2)); Collections.sort(marvelHeroes, Comparator.naturalOrder()); marvelHeroes.forEach(System.out::print); } }/<code>
有關內部類的更多信息
一個匿名內部類是單純的任何類,它的名稱並不重要,並實現我們宣佈的接口。因此,在示例中,新Comparator的實際上是一個沒有名稱的類的實例化,該類使用所需的邏輯來實現該方法。
將Comparator與lambda表達式一起使用
匿名內部類非常冗長,這可能會導致我們的代碼出現問題。在Comparator界面中,我們可以使用lambda表達式簡化並簡化代碼。例如,我們可以更改此:
<code> Collections.sort(marvel, new Comparator() { @Override public int compare(String hero1, String hero2) { return hero1.compareTo(hero2); } });/<code>
對此:
<code> Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));/<code>
更少的代碼和相同的結果!
該代碼的輸出為:
<code> Cyclops SpiderMan Wolverine Xavier /<code>
通過更改此代碼,我們可以使代碼更簡單:
<code> Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));/<code>
對此:
<code> Collections.sort(marvel, Comparator.naturalOrder());/<code>
核心Java類是否可比?
許多核心Java類和對象都實現了該Comparable接口,這意味著我們不必compareTo()為這些類實現邏輯。以下是一些熟悉的示例:
串
<code> public final class String implements java.io.Serializable, Comparable, CharSequence { .../<code>
整數
<code> public final class Integer extends Number implements Comparable { …/<code>
雙
<code> public final class Double extends Number implements Comparable {.../<code>
還有很多。我鼓勵您探索Java核心類,以學習它們的重要模式和概念。
接受可比接口挑戰!
通過弄清楚以下代碼的輸出來測試您學到了什麼。請記住,如果僅通過學習自己解決挑戰,就會學得最好。找到答案後,您可以檢查以下答案。您也可以運行自己的測試以完全吸收這些概念。
<code> public class SortComparableChallenge { public static void main(String... doYourBest) { Set set = new TreeSet<>(); set.add(new Simpson("Homer")); set.add(new Simpson("Marge")); set.add(new Simpson("Lisa")); set.add(new Simpson("Bart")); set.add(new Simpson("Maggie")); List list = new ArrayList<>(); list.addAll(set); Collections.reverse(list); list.forEach(System.out::println); } static class Simpson implements Comparable { String name; public Simpson(String name) { this.name = name; } public int compareTo(Simpson simpson) { return simpson.name.compareTo(this.name); } public String toString() { return this.name; } } }/<code>
該代碼的輸出是什麼?
<code> A) Bart Homer Lisa Maggie Marge B) Maggie Bart Lisa Marge Homer C) Marge Maggie Lisa Homer Bart D) Indeterminate/<code>
TreeSet和reverse()
查看代碼,您應該注意到的第一件事是我們正在使用TreeSet,因此元素將自動排序。第二件事是比較的順序顛倒了,所以排序將以相反的順序進行。
當我們第一次將元素添加到列表中時,會TreeSet自動將它們排序為:
<code> Marge Maggie Lisa Homer Bart/<code>
然後,我們使用該reverse()方法,該方法可以反轉元素的順序。因此,最終輸出將是:
<code> Bart Homer Lisa Maggie Marge/<code>
可比性的常見錯誤
嘗試對方法中的不可比較對象進行排序sort()。使用Comparable同一對象中不同的排序策略。反轉compareTo()方法中的比較,以便排序將採用相反的順序,例如:正常順序: public int compareTo(Simpson simpson) { this.name.compareTo(simpson.name); }