用Java中的Comparable和Comparator排序

可比還是可比?為您需要的排序算法選擇正確的接口

用Java中的Comparable和Comparator排序

程序員經常需要將數據庫中的元素排序到集合,數組或映射中。在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()工作原理:

用Java中的Comparable和Comparator排序

我們只能使用與該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); }倒序: public int compareTo(Simpson simpson) { simpson.name.compareTo(this.name); }
  • Comparable在TreeSet或TreeMap對象中添加不可比較的(任何未實現的對象)對象。

使用Java排序時要記住的幾點

  • Comparable當比較是給定類的標準時使用。
  • Comparator需要更大靈活性時使用。
  • 可以將lambdas與一起使用Comparator。
  • 許多Java核心類都實現了Comparable。
  • 使用TreeMap或TreeSet排序時,一個Map或一個Set。
  • 該compareTo()方法適用於Comparable和Comparator。
  • compareTo()如果一個對象大於另一個對象,則該方法將返回一個正數,如果一個對象小於另一個,則返回負數,如果它們相同則返回零。


分享到:


相關文章: