CodeGym /Java Blog /Toto sisi /Java 的比較器接口
John Squirrels
等級 41
San Francisco

Java 的比較器接口

在 Toto sisi 群組發布
懶惰的人並不是唯一用 Java 編寫比較器和比較的人。我並不懶惰,所以請喜歡並抱怨另一種解釋。我希望這不會是多餘的。是的,這篇文章就是對這個問題的回答:“你能從記憶中寫出一個比較器嗎? ”我希望每個人在閱讀這篇文章後都能從記憶中寫出一個比較器。 Javas 比較器接口 - 1

介紹

如您所知,Java 是一種面向對象的語言。因此,習慣於在 Java 中操作對象。但遲早,您將面臨根據某些特徵比較對象的任務。 例如:假設我們有Message類描述的一些消息:

public static class Message {
    private String message;
    private int id;
        
    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
將此類放入Tutorialspoint Java 編譯器中。不要忘記添加導入語句:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
在該main方法中,創建幾條消息:

public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
我們想一想,如果我們要比較它們,我們會怎麼做?比如我們想按id排序。為了創建一個順序,我們需要以某種方式比較對象,以便了解哪個對象應該先出現(即較小的),哪個對象應該在後面(即較大的)。讓我們從像java.lang.Object這樣的類開始。我們知道所有的類都隱式繼承了Object類。這是有道理的,因為它反映了“一切都是對象”的概念,並為所有類提供了共同的行為。這個類規定每個類都有兩個方法:→hashCodehashCode方法返回一些數字(int) 對象的表示。這意味著什麼?這意味著如果您創建一個類的兩個不同實例,那麼它們應該具有不同的hashCodes。該方法的描述盡可能多:“在相當實用的情況下,類 Object 定義的 hashCode 方法確實為不同的對象返回不同的整數”。也就是說,對於兩個不同的instances,應該有不同的hashCodes。也就是說,這種方法不適合我們的比較。→ equals。該equals方法回答了“這些對像是否相等?”的問題。並返回一個boolean." 默認情況下,此方法具有以下代碼:

public boolean equals(Object obj) {
    return (this == obj);
}
也就是說,如果這個方法沒有被覆蓋,它本質上是說對象引用是否匹配。這不是我們想要的消息,因為我們對消息 ID 感興趣,而不是對象引用。即使我們覆蓋了這個equals方法,我們最多也只能希望知道它們是否相等。這還不足以讓我們確定順序。那我們需要什麼呢?我們需要比較的東西。比較的是a Comparator。打開Java API並找到Comparator。確實有java.util.Comparator接口 java.util.Comparator and java.util.Comparable 如您所見,存在這樣的接口。實現它的類說:“我實現了一個比較對象的方法。” 您真正需要記住的唯一一件事是比較器合約,其表達如下:

Comparator returns an int according to the following rules: 
  • It returns a negative int if the first object is smaller
  • It returns a positive int if the first object is larger
  • It returns zero if the objects are equal
現在讓我們寫一個比較器。我們需要導入java.util.Comparator. 在 import 語句之後,將以下內容添加到方法中mainComparator<Message> comparator = new Comparator<Message>(); 當然,這行不通,因為Comparator是一個接口。{}所以我們在括號後加上大括號。在大括號內寫下如下方法:

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
您甚至不需要記住拼寫。比較器是執行比較的人,也就是說,它進行比較。為了指示對象的相對順序,我們返回一個int. 基本上就是這樣。好,易於。從例子中可以看出,除了 Comparator 之外,還有一個接口—— java.lang.Comparable,需要我們實現該compareTo方法。這個接口說,“一個實現我的類可以比較類的實例。” 例如ToInteger的實現compare如下:

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 引入了一些不錯的變化。如果您仔細查看界面Comparator,您會看到@FunctionalInterface其上方的註釋。此註釋僅供參考,告訴我們此接口是可用的。也就是說這個接口只有1個抽象方法,也就是沒有實現的方法。這給了我們什麼?現在我們可以像這樣編寫比較器的代碼:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
我們在括號中命名變量。Java會看到,因為只有一個方法,那麼需要輸入參數的個數和類型就很清楚了。然後我們使用箭頭運算符將它們傳遞給這部分代碼。更重要的是,多虧了 Java 8,我們現在在接口中有了默認方法。當我們實現一個接口時,這些方法默認出現。界面Comparator有幾個。例如:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
還有另一種方法可以使您的代碼更清晰。看看上面的示例,我們在其中定義了我們的比較器。它有什麼作用?這很原始。它只是獲取一個對象並提取一些“可比較”的值。例如,Integerimplements comparable,因此我們能夠對消息 id 字段的值執行 compareTo 操作。這個簡單的比較器函數可以這樣寫:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
換句話說,我們有一個Comparator這樣比較的對象:它獲取對象,使用getId()方法從對像中獲取 a Comparable,然後使用對象compareTo進行比較。並且沒有更多可怕的結構。最後,我想再提一個特點。比較器可以鏈接起來。例如:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

應用

聲明一個比較器被證明是合乎邏輯的,你不覺得嗎?現在我們需要看看如何以及在哪裡使用它。→Collections.sort(java.util.Collections) 當然,我們可以用這種方式對集合進行排序。但不是每一個集合,只有列表。這裡沒有什麼不尋常的,因為列表是一種集合,您可以在其中通過索引訪問元素。這允許第二個元素與第三個元素交換。這就是為什麼下面的排序方法只適用於列表:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) 數組也很容易排序。同樣,出於同樣的原因——它們的元素是通過索引訪問的。→Descendants of java.util.SortedSet and java.util.SortedMap 你會記得,Set並且Map不保證元素存儲的順序。但是,我們有特殊的實現來保證順序。如果集合的元素沒有實現java.util.Comparable,那麼我們可以將 a 傳遞Comparator給它的構造函數:

Set<Message> msgSet = new TreeSet(comparator);
Stream API 在 Java 8 中出現的 Stream API 中,比較器讓您可以簡化流元素的工作。例如,假設我們需要一個從 0 到 999 的隨機數序列,包括:

Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
我們可以到此為止,但還有更有趣的問題。例如,假設您需要準備一個Map,其中 key 是消息 id。此外,我們要對這些鍵進行排序,因此我們將從以下代碼開始:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
我們實際上得到了HashMap這裡。正如我們所知,它不保證任何順序。結果,我們按 id 排序的元素完全失去了它們的順序。不好。我們必須稍微改變我們的收集器:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
代碼開始看起來有點嚇人,但現在問題已經正確解決了。在此處閱讀有關各種分組的更多信息: 您可以創建自己的收集器。在此處閱讀更多內容:“在 Java 8 中創建自定義收集器”。您將受益於閱讀此處的討論:“Java 8 list to map with stream”

陷阱

Comparator並且Comparable很好。但是您應該記住一個細微差別。當一個類執行排序時,它期望你的類可以轉換為一個Comparable. 如果不是這種情況,那麼您將在運行時收到錯誤。讓我們看一個例子:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
似乎這裡沒有什麼問題。但實際上,在我們的示例中,它會失敗並出現錯誤: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable 這一切都是因為它試圖對元素進行排序(SortedSet畢竟它是一個 )...但不能。使用SortedMapand時不要忘記這一點SortedSet

更多閱讀:

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION