CodeGym /Java-Blog /Random-DE /Javas Comparator-Schnittstelle
Autor
Artem Divertitto
Senior Android Developer at United Tech

Javas Comparator-Schnittstelle

Veröffentlicht in der Gruppe Random-DE
Nicht nur die Faulen schreiben über Komparatoren und Vergleiche in Java. Ich bin nicht faul, also liebe und beschwere dich bitte über eine weitere Erklärung. Ich hoffe, es wird nicht überflüssig sein. Und ja, dieser Artikel ist die Antwort auf die Frage: „ Können Sie einen Komparator aus dem Gedächtnis schreiben? “ Ich hoffe, dass jeder nach der Lektüre dieses Artikels in der Lage sein wird, einen Komparator aus dem Gedächtnis zu schreiben. Javas Comparator-Schnittstelle – 1

Einführung

Wie Sie wissen, ist Java eine objektorientierte Sprache. Daher ist es in Java üblich, Objekte zu manipulieren. Doch früher oder später steht man vor der Aufgabe, Objekte anhand bestimmter Merkmale zu vergleichen. Beispiel : Angenommen, wir haben eine von der Klasse beschriebene Nachricht 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;
    }
}
Fügen Sie diese Klasse in den Tutorialspoint Java-Compiler ein . Vergessen Sie nicht, auch die Importanweisungen hinzuzufügen:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Erstellen Sie in der mainMethode mehrere Nachrichten:

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);
}
Überlegen wir mal, was wir tun würden, wenn wir sie vergleichen wollten? Wir möchten zum Beispiel nach ID sortieren. Und um eine Ordnung zu schaffen, müssen wir die Objekte irgendwie vergleichen, um zu verstehen, welches Objekt zuerst kommen sollte (dh das kleinere) und welches folgen sollte (dh das größere). Beginnen wir mit einer Klasse wie java.lang.Object . Wir wissen, dass alle Klassen implizit die ObjectKlasse erben. Und das macht Sinn, weil es das Konzept widerspiegelt, dass „alles ein Objekt ist“ und ein gemeinsames Verhalten für alle Klassen bietet. Diese Klasse schreibt vor, dass jede Klasse zwei Methoden hat: → hashCode Die hashCodeMethode gibt einen numerischen Wert zurück (int) Darstellung des Objekts. Was bedeutet das? Das bedeutet, dass, wenn Sie zwei verschiedene Instanzen einer Klasse erstellen, diese unterschiedliche hashCodes haben sollten. In der Beschreibung der Methode heißt es: „Soweit es einigermaßen praktikabel ist, gibt die durch die Klasse Object definierte Methode hashCode unterschiedliche Ganzzahlen für unterschiedliche Objekte zurück.“ Mit anderen Worten: Für zwei verschiedene instances sollte es unterschiedliche hashCodes geben. Das heißt, diese Methode ist für unseren Vergleich nicht geeignet. → equals. Die equalsMethode beantwortet die Frage „Sind diese Objekte gleich?“ und gibt ein zurück boolean. Standardmäßig hat diese Methode den folgenden Code:

public boolean equals(Object obj) {
    return (this == obj);
}
Das heißt, wenn diese Methode nicht überschrieben wird, sagt sie im Wesentlichen aus, ob die Objektreferenzen übereinstimmen oder nicht. Das ist nicht das, was wir für unsere Nachrichten wollen, weil wir an Nachrichten-IDs und nicht an Objektreferenzen interessiert sind. Und selbst wenn wir die equalsMethode außer Kraft setzen, können wir höchstens hoffen, herauszufinden, ob sie gleich sind. Und das reicht uns nicht aus, um die Reihenfolge festzulegen. Was brauchen wir also? Wir brauchen etwas Vergleichbares. Derjenige, der vergleicht, ist ein Comparator. Öffnen Sie die Java-API und suchen Sie nach Comparator . Tatsächlich gibt es eine java.util.ComparatorSchnittstelle java.util.Comparator and java.util.Comparable Wie Sie sehen, existiert eine solche Schnittstelle. Eine Klasse, die es implementiert, sagt: „Ich implementieren eine Methode, die Objekte vergleicht.“ Das Einzige, was Sie wirklich beachten müssen, ist der Vergleichsvertrag, der wie folgt lautet:

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
Schreiben wir nun einen Komparator. Wir müssen importieren java.util.Comparator. Fügen Sie der mainMethode nach der Importanweisung Folgendes hinzu: Comparator<Message> comparator = new Comparator<Message>(); Natürlich wird dies nicht funktionieren, da Comparatores sich um eine Schnittstelle handelt. {}Deshalb fügen wir nach den Klammern geschweifte Klammern ein . Schreiben Sie die folgende Methode in die geschweiften Klammern:

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Sie müssen sich nicht einmal die Schreibweise merken. Ein Komparator ist jemand, der einen Vergleich durchführt, also vergleicht. Um die relative Reihenfolge der Objekte anzugeben, geben wir eine zurück int. Das ist es im Grunde. Schön und einfach. Wie Sie dem Beispiel entnehmen können, gibt es neben Comparator noch eine weitere Schnittstelle – java.lang.Comparable, für die wir die Methode implementieren müssen compareTo. Diese Schnittstelle sagt: „Eine Klasse, die mich implementiert, ermöglicht den Vergleich von Instanzen der Klasse.“ IntegerDie Implementierung von To lautet beispielsweise comparewie folgt:

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 hat einige nette Änderungen eingeführt. Wenn Sie sich die Benutzeroberfläche genauer ansehen Comparator, sehen Sie die @FunctionalInterfaceAnmerkung darüber. Diese Anmerkung dient Informationszwecken und teilt uns mit, dass diese Schnittstelle funktionsfähig ist. Das bedeutet, dass diese Schnittstelle nur eine abstrakte Methode hat, also eine Methode ohne Implementierung. Was bringt uns das? Jetzt können wir den Code des Komparators wie folgt schreiben:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Wir benennen die Variablen in Klammern. Da es nur eine Methode gibt, erkennt Java, dass die erforderliche Anzahl und Art der Eingabeparameter klar ist. Dann verwenden wir den Pfeiloperator, um sie an diesen Teil des Codes zu übergeben. Darüber hinaus verfügen wir dank Java 8 jetzt über Standardmethoden in Schnittstellen. Diese Methoden werden standardmäßig angezeigt, wenn wir eine Schnittstelle implementieren. Die ComparatorSchnittstelle verfügt über mehrere. Zum Beispiel:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Es gibt eine andere Methode, die Ihren Code sauberer macht. Schauen Sie sich das Beispiel oben an, in dem wir unseren Komparator definiert haben. Was tut es? Es ist ziemlich primitiv. Es nimmt einfach ein Objekt und extrahiert einen Wert, der „vergleichbar“ ist. IntegerImplementiert beispielsweise comparable, sodass wir eine „compareTo“-Operation für die Werte von Nachrichten-ID-Feldern durchführen können. Diese einfache Komparatorfunktion kann wie folgt geschrieben werden:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Mit anderen Worten, wir haben ein Objekt, Comparatordas wie folgt vergleicht: Es nimmt Objekte, verwendet die getId()Methode, um ein Comparablevon ihnen zu erhalten, und verwendet es dann compareTozum Vergleichen. Und es gibt keine schrecklicheren Konstrukte mehr. Und zum Schluss möchte ich noch eine Besonderheit erwähnen. Komparatoren können verkettet werden. Zum Beispiel:

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

Anwendung

Einen Komparator zu deklarieren, erweist sich als ziemlich logisch, finden Sie nicht? Jetzt müssen wir sehen, wie und wo wir es verwenden. → Collections.sort(java.util.Collections) Selbstverständlich können wir Sammlungen auf diese Weise sortieren. Aber nicht jede Sammlung, nur Listen. Das ist nichts Ungewöhnliches, denn Listen sind die Art von Sammlungen, in denen Sie über ihren Index auf Elemente zugreifen. Dadurch kann das zweite Element mit dem dritten Element ausgetauscht werden. Deshalb gilt die folgende Sortiermethode nur für Listen:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) Arrays sind auch einfach zu sortieren. Auch hier aus dem gleichen Grund: Der Zugriff auf ihre Elemente erfolgt über den Index. → Descendants of java.util.SortedSet and java.util.SortedMap Sie werden sich daran erinnern Setund Mapkeine Garantie für die Reihenfolge übernehmen, in der Elemente gespeichert werden. ABER wir haben spezielle Implementierungen, die die Reihenfolge garantieren. Und wenn die Elemente einer Sammlung nicht implementiert sind java.util.Comparable, können wir a Comparatoran ihren Konstruktor übergeben:

Set<Message> msgSet = new TreeSet(comparator);
Stream API In der Stream-API, die in Java 8 erschien, können Sie mit Komparatoren die Arbeit mit Stream-Elementen vereinfachen. Angenommen, wir benötigen eine Folge von Zufallszahlen von 0 bis einschließlich 999:

Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Wir könnten hier aufhören, aber es gibt noch mehr interessante Probleme. Angenommen, Sie müssen eine erstellen Map, bei der der Schlüssel eine Nachrichten-ID ist. Außerdem möchten wir diese Schlüssel sortieren, also beginnen wir mit dem folgenden Code:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Wir bekommen HashMaphier tatsächlich eine. Und wie wir wissen, garantiert es keine Ordnung. Dadurch verlieren unsere Elemente, die nach ID sortiert wurden, einfach ihre Reihenfolge. Nicht gut. Wir müssen unseren Sammler etwas ändern:

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));
Der Code sieht etwas gruseliger aus, aber jetzt ist das Problem korrekt gelöst. Lesen Sie hier mehr über die verschiedenen Gruppierungen: Sie können Ihren eigenen Sammler erstellen. Lesen Sie hier mehr: „Erstellen eines benutzerdefinierten Kollektors in Java 8“ . Und Sie werden von der Lektüre der Diskussion hier profitieren: „Java 8-Liste zum Zuordnen mit Stream“ .

Fallfalle

Comparatorund Comparablesind gut. Aber es gibt eine Nuance, die Sie beachten sollten. Wenn eine Klasse eine Sortierung durchführt, erwartet sie, dass Ihre Klasse in eine konvertiert werden kann Comparable. Ist dies nicht der Fall, erhalten Sie zur Laufzeit eine Fehlermeldung. Schauen wir uns ein Beispiel an:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Es scheint, dass hier nichts falsch ist. Aber tatsächlich wird es in unserem Beispiel mit einem Fehler fehlschlagen: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Und das alles, weil es versucht hat, die Elemente zu sortieren (es ist SortedSetschließlich ein ) ... aber es konnte nicht. Vergessen Sie dies nicht, wenn Sie mit SortedMapund arbeiten SortedSet.

Mehr Lektüre:

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