天天看點

用Java中的Comparable和Comparator排序

程式員經常需要将資料庫中的元素排序到集合,數組或映射中。 在Java中,我們可以實作任何類型的排序算法。 使用

Comparable

接口和

compareTo()

方法,我們可以使用字母順序,

String

長度,反向字母順序或數字進行排序。

Comparator

界面允許我們以更靈活的方式執行相同操作。

無論我們想做什麼,我們隻需要知道如何為給定的接口和類型實作正确的排序邏輯即可。

擷取源代碼

擷取此Java Challenger 的代碼 。 在遵循示例的同時,您可以運作自己的測試。

用自定義對象對Java清單進行排序

在我們的示例中,我們将使用到目前為止與其他Java Challenger相同的POJO。 在第一個示例中,我們使用通用類型的

Simpson

Simpson

類中實作Comparable接口:

class Simpson implements Comparable<Simpson> {
    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<SimpsonCharacter> 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);
    }

}
                

請注意,我們已經重寫了compareTo()方法并傳遞了另一個

Simpson

對象。 我們還重寫了

toString()

方法,隻是為了使示例易于閱讀。

toString

方法顯示該對象的所有資訊。 當我們列印對象時,輸出将是在

toString()

compareTo()方法

compareTo()

方法将給定對象或目前執行個體與指定對象進行比較,以确定對象的順序。 快速浏覽

compareTo()

工作原理:

如果比較傳回 然後 ...

>= 1

this.name > simpson.name

this.name == simpson.name

<= -1

this.name < simpson.name

我們隻能使用與

sort()

方法相當的

sort()

。 如果我們嘗試傳遞未實作

Comparable

Simpson

,則會收到編譯錯誤。

sort()

方法通過傳遞

Comparable

任何對象來使用多态 。 然後将按預期對對象進行排序。

先前代碼的輸出為:

Bart Homer Lisa Marge 
                

如果我們想颠倒順序,我們可以将

sort()

換成

reverse()

; 從:

Collections.sort(simpsons);
                

至:

Collections.reverse(simpsons);
                

部署

reverse()

方法會将先前的輸出更改為:

Marge Lisa Homer Bart 
                

排序Java數組

在Java中,我們可以對數組進行任意排序,隻要它實作

Comparable

接口即可。 這是一個例子:

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);
    }
}
                

在第一個

sort()

調用中,将數組排序為:

1 6 7 8 9
                

在第二次

sort()

調用中,将其排序為:

Homer Lisa
                

請記住,自定義對象必須實作

Comparable

才能進行排序,即使是數組也是如此。

我可以對沒有可比對象的對象進行排序嗎?

如果Simpson對象未實作

Comparable

,則将抛出ClassCastException 。 如果将其作為測試運作,您将看到類似以下輸出的内容:

Error:(16, 20) java: no suitable method found for sort(java.util.List<com.javaworld.javachallengers.sortingcomparable.Simpson>)
    method java.util.Collections.<T>sort(java.util.List<T>) 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.<T>sort(java.util.List<T>,java.util.Comparator<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))
                

該日志可能令人困惑,但請不要擔心。 請記住,任何未實作

Comparable

接口的已排序對象都将引發

ClassCastException

使用TreeMap對地圖排序

Java API包括許多有助于排序的類,包括TreeMap 。 在下面的示例中,我們使用

TreeMap

将鍵排序到

Map

public class TreeMapExample {

    public static void main(String... barney) {
        Map<SimpsonCharacter, String> 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);
    }
}
                

TreeMap

使用

Comparable

接口實作的

compareTo()

方法。 生成的

Map

中的每個元素均按其鍵排序。 在這種情況下,輸出為:

Barney=beer, Homer=television, Lenny=Carl, Moe=shotgun
                

但是請記住:如果對象未實作

Comparable

,則将抛出

ClassCastException

使用TreeSet對集合進行排序

Set

接口負責存儲唯一值,但是當我們使用TreeSet實作時,插入的元素将在添加它們時自動排序:

public class TreeSetExample {

    public static void main(String... barney) {
        Set<SimpsonCharacter> 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);
    }
}
                

此代碼的輸出是:

Barney, Homer, Lenny, Moe
                

同樣,如果我們使用的對象不是

Comparable

,則将抛出

ClassCastException

用比較器排序

如果我們不想使用POJO類中的相同

compareTo()

方法怎麼辦? 我們可以重寫

Comparable

方法以使用其他邏輯嗎? 下面是一個示例:

public class BadExampleOfComparable {

    public static void main(String... args) {
        List<SimpsonCharacter> 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);
    }

}
                

如您所見,此代碼很複雜,并且包含很多重複。 對于相同的邏輯,我們必須兩次重寫

compareTo()

方法。 如果還有更多元素,我們将不得不為每個對象複制邏輯。

幸運的是,我們具有Comparator接口,該接口使我們可以将

compareTo()

邏輯與Java類分離。 考慮上面使用

Comparator

重寫的同一示例:

public class GoodExampleOfComparator {

    public static void main(String... args) {
        List<SimpsonCharacter> characters = new ArrayList<>();

        SimpsonCharacter homer = new SimpsonCharacter("Homer");
        SimpsonCharacter moe = new SimpsonCharacter("Moe");

        characters.add(homer);
        characters.add(moe);

        Collections.sort(characters, (Comparator.<SimpsonCharacter>
                        comparingInt(character1 -> character1.name.length())
                        .thenComparingInt(character2 -> character2.name.length())));

        System.out.println(characters);
    }
}
                

這些示例說明了

Comparable

Comparator

之間的主要差別。

當對象有一個預設的預設比較時,請使用

Comparable

。 使用

Comparator

時,你需要解決現有

compareTo()

或者當你需要使用特定的邏輯更靈活的方式。

Comparator

從您的對象分離排序邏輯,并在

sort()

方法内包含

compareTo()

邏輯。

将Comparator與匿名内部類一起使用

在下一個示例中,我們使用匿名内部類比較對象的值。 在這種情況下, 匿名内部類是實作

Comparator

任何類。 使用它意味着我們不必執行個體化實作接口的命名類。 相反,我們在匿名内部類中實作了

compareTo()

方法。

public class MarvelComparator {

    public static void main(String... comparator) {
        List<String> marvelHeroes = new ArrayList<>();

        marvelHeroes.add("SpiderMan ");
        marvelHeroes.add("Wolverine ");
        marvelHeroes.add("Xavier ");
        marvelHeroes.add("Cyclops ");


        Collections.sort(marvelHeroes, new Comparator<String>() {
            @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);
    }
}
                

有關内部類的更多資訊

匿名内部類就是名稱無關緊要且實作了我們聲明的接口的任何類。 是以,在該示例中,新的

Comparator

實際上是一個沒有名稱的類的執行個體化,該類使用所需的邏輯來實作該方法。

将Comparator與lambda表達式一起使用

匿名内部類非常冗長,這可能會導緻我們的代碼出現問題。 在

Comparator

界面中,我們可以使用lambda表達式來簡化代碼并使代碼更易于閱讀。 例如,我們可以更改此:

Collections.sort(marvel, new Comparator<String>() {
            @Override
            public int compare(String hero1, String hero2) {
                return hero1.compareTo(hero2);
            }
        });
                

對此:

Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));
                

更少的代碼和相同的結果!

該代碼的輸出為:

Cyclops SpiderMan Wolverine Xavier 
                

通過更改此代碼,我們可以使代碼更簡單:

Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));
                

對此:

Collections.sort(marvel, Comparator.naturalOrder());
                

Java中的Lambda表達式

了解有關Java中的lambda表達式和其他功能程式設計技術的更多資訊。

核心Java類是否可比?

許多核心Java類和對象都實作

Comparable

接口,這意味着我們不必為這些類實作

compareTo()

邏輯。 以下是一些熟悉的示例:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence { ...
                

整數

public final class Integer extends Number implements Comparable<Integer> { …
                

public final class Double extends Number implements Comparable<Double> {...
                

還有很多。 我鼓勵您探索Java核心類,以學習它們的重要模式和概念。

接受可比接口挑戰!

通過弄清楚以下代碼的輸出來測試您學到了什麼。 請記住,如果僅通過學習自己解決挑戰,您将學得最好。 找到答案後,您可以檢查以下答案。 您也可以運作自己的測試以完全吸收這些概念。

public class SortComparableChallenge {

    public static void main(String... doYourBest) {
        Set<Simpson> 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<Simpson> list = new ArrayList<>();
        list.addAll(set);
        Collections.reverse(list);
        list.forEach(System.out::println);
    }

    static class Simpson implements Comparable<Simpson> {
        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;
        }
    }
}
                

該代碼的輸出是什麼?

A)   Bart
       Homer
       Lisa
       Maggie
       Marge

B)   Maggie
       Bart
       Lisa
       Marge
       Homer

C)   Marge
       Maggie
       Lisa
       Homer
       Bart

D)   Indeterminate
                
翻譯自: https://www.infoworld.com/article/3323403/java-challengers-5-sorting-with-comparable-and-comparator-in-java.html