JavaRush /Java Blog /Random-TW /Java 中的比較器
Viacheslav
等級 3

Java 中的比較器

在 Random-TW 群組發布
只有懶人才沒有寫過Java中的Comparator和比較。我並不懶惰 - 所以我請你喜歡並喜歡另一種變體。我希望這不會是多餘的。是的,這篇文章就是這個問題的答案:“你能憑記憶編寫一個比較器嗎?” 我希望讀完這篇文章後,每個人都能憑記憶寫一個比較器。
Java 中的比較器 - 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 類別。這是合乎邏輯的,因為 這本質上表達了「一切都是物件」的概念,並為所有類別提供了共同的行為。這個類別定義每個類別有兩個方法: → hashCode hashCode 方法傳回物件的一些數字(int)表示形式作為類別的實例。這是什麼意思?這意味著如果您創建了一個類別的兩個不同實例,那麼由於實例不同,它們的 hashCode 應該不同。這就是該方法的描述中所說的:「盡可能合理實用,由類別Object 定義的hashCode 方法確實為不同的物件傳回不同的整數」也就是說,如果這是兩個不同的實例,那麼它們應該具有不同的值。雜湊碼。也就是說,這個方法不適合我們的比較。→ equals equals 方法回答「物件是否相等」的問題並傳回一個布林值。此方法有預設代碼:
public boolean equals(Object obj) {
    return (this == obj);
}
也就是說,在不重寫物件上的此方法的情況下,此方法本質上說明對物件的引用是否匹配。這不適合我們的消息,因為我們對物件的連結不感興趣,我們對訊息 id 感興趣。即使我們重寫 equals 方法,我們得到的最大值也是:「它們相等」或「它們不相等」。但這還不足以讓我們確定順序。

Java中的比較器和Comparable

什麼適合我們?如果我們在翻譯器中將「compare」這個字翻譯成英文,我們就會得到「compare」的翻譯。太好了,那我們需要一個能夠比較的人。如果你比較這個compare的話,那麼比較的就是Comparator。讓我們打開Java Api並找到Comparator。確實,有這樣一個介面 - java.util.Comparator java.util.Comparator 和 java.lang.Comparable 正如你所看到的,有這樣一個介面。實現它的類別說“我正在實作一個用於比較物件的函數”。唯一真正需要記住的是比較器合約,其表達如下:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
現在讓我們來寫一個比較器。我們需要導入java.util.Comparator。import之後,在main中加入一個方法: Comparator<Message> comparator = new Comparator<Message>(); 自然,這樣是不行的,因為 比較器是一個介面。因此,在括號後面我們將添加花括號{ }。在這些括號中,我們將編寫方法:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
你甚至不必記住寫這篇。比較器是執行比較的人,即進行比較。為了回答比較物件的順序問題,我們傳回 int。事實上,僅此而已。簡單又容易。從範例中我們可以看到,除了 Comparator 之外,還有另一個介面 - java.lang.Comparable,實作它時我們必須定義compareTo方法。此介面表示「實作介面的類別允許比較該類別的實例」。例如,Integer 的compareTo 實作如下所示:
(x < y) ? -1 : ((x == y) ? 0 : 1)
如何記住所有這些介面?做什麼的?一切都來自英語。Compare-比較,比較的人是Comparator(例如註冊員,也就是註冊的人),形容詞「比較」是Comparable。那麼,「Compare with」不但可以翻譯為compare with,還可以翻譯為compare to。這很簡單。Java語言是由講英語的人編寫的,在Java中命名一切時,他們只是以英語為指導,並且命名中有某種邏輯。compareTo 方法描述如何將類別的實例與其他實例進行比較。例如,字串按字典順序比較,數字按值比較。
Java 中的比較器 - 2
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();
還有另一種方法可以讓您的程式碼更清晰。讓我們看一下上面的例子,我們在其中描述了我們的比較器。他在做什麼?這是相當原始的。它只是獲取一個物件並從中提取一些可比較的值。例如,Integer 實作了可比較,因此我們能夠對訊息 id 值執行compareTo。這個簡單的比較器函數也可以這樣寫:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
也就是說,從字面上看,“我們有一個像這樣進行比較的 Comparator:它獲取對象,使用 getId() 方法從中獲取 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) 陣列排序也很方便。同樣,出於透過索引存取元素的相同原因。→ java.util.SortedSet 和 java.util.SortedMap 的後代 我們記得,Set 和 Map 不保證儲存記錄的順序。但我們有特殊的實作來保證順序。如果集合元素沒有實作 java.lang.Comparable,那麼我們可以將 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));
程式碼看起來有點令人毛骨悚然,但由於 TreeMap 的明確實現,問題現在已正確解決。您可以在這裡閱讀有關各個團體的更多資訊: 您可以自行建立收集器。您可以在此處閱讀更多內容:“在 Java 8 中建立自訂收集器”。閱讀這裡的討論很有用:“Java 8 list to map with stream”
Java 中的比較器 - 3
比較器和可比耙子 都很好。但有一個與它們相關的細微差別值得記住。當一個類別執行排序時,它會計算出它可以將您的類別轉換為 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)。但我不能。使用 SortedMap 和 SortedSet 時應該記住這一點。 另外 推薦觀看: Yuri Tkach:HashSet 和 TreeSet - Collections #1 - Advanced Java
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION