JavaRush /Java-Blog /Random-DE /Komparator in Java
Viacheslav
Level 3

Komparator in Java

Veröffentlicht in der Gruppe Random-DE
Nur faule Leute haben nicht über Comparator und Vergleich in Java geschrieben. Ich bin nicht faul – deshalb bitte ich Sie, eine weitere Variante zu lieben und zu bevorzugen. Ich hoffe, dass es nicht überflüssig wird. Und ja, dieser Artikel ist die Antwort auf die Frage: „Kann man 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.
Komparator in Java - 1
Einführung Java ist bekanntermaßen eine objektorientierte Sprache. Daher ist es in Java üblich, mit Objekten zu operieren. Aber früher oder später stellt sich die Aufgabe, Objekte nach einem Prinzip zu vergleichen. Also, gegeben: Wir haben eine Nachricht, die von der Message-Klasse beschrieben wird:
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 wir diese Klasse zum Tutorialspoint Java Compiler hinzu . Denken wir auch daran, Importe hinzuzufügen:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
In der Hauptmethode erstellen wir 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);
}
Lassen Sie uns darüber nachdenken, was wir tun sollten, wenn wir sie vergleichen möchten. Wir möchten zum Beispiel nach ID sortieren. Und um Ordnung zu schaffen, müssen Sie Objekte irgendwie vergleichen, um zu verstehen, welches Objekt vorhergeht (also kleiner) und welches als nächstes kommt (also größer). Beginnen wir mit einer Klasse wie java.lang.Object . Wie wir wissen, erben alle Klassen implizit von dieser Object-Klasse. Und das ist logisch, denn Dies drückt im Wesentlichen das Konzept aus: „Alles ist ein Objekt“ und sorgt für ein gemeinsames Verhalten aller Klassen. Und diese Klasse definiert, dass jede Klasse zwei Methoden hat: → hashCode Die Methode hashCode gibt eine numerische (int) Darstellung des Objekts als Instanz der Klasse zurück. Was bedeutet das? Das heißt, wenn Sie zwei verschiedene Instanzen einer Klasse erstellt haben, sollte ihr Hash-Code unterschiedlich sein, da die Instanzen unterschiedlich sind. 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.“ Das heißt, wenn es sich um zwei verschiedene Instanzen handelt, sollten sie unterschiedliche haben HashCodes. Das heißt, diese Methode ist für unseren Vergleich nicht geeignet. → equal Die Methode equal beantwortet die Frage „Sind Objekte gleich“ und gibt einen booleschen Wert zurück. Diese Methode hat den Standardcode:
public boolean equals(Object obj) {
    return (this == obj);
}
Das heißt, ohne diese Methode für ein Objekt zu überschreiben, sagt diese Methode im Wesentlichen aus, ob die Verweise auf das Objekt übereinstimmen oder nicht. Dies ist für unsere Nachrichten nicht geeignet, da wir nicht an Links zum Objekt interessiert sind, sondern an der Nachrichten-ID. Und selbst wenn wir die Methode „equals“ überschreiben, würden wir maximal Folgendes erhalten: „Sie sind gleich“ oder „Sie sind nicht gleich.“ Dies reicht uns jedoch nicht aus, um die Reihenfolge festzulegen.

Komparator und Vergleichbar in Java

Was passt zu uns? Wenn wir im Übersetzer das Wort „compare“ ins Englische übersetzen, erhalten wir die Übersetzung „compare“. Super, dann brauchen wir jemanden, der vergleicht. Wenn Sie diesen Vergleich vergleichen, dann ist derjenige, der vergleicht, der Komparator. Öffnen wir die Java-API und suchen dort nach Comparator . Und tatsächlich gibt es eine solche Schnittstelle – java.util.Comparator, java.util.Comparator und java.lang.Comparable. Wie Sie sehen, gibt es eine solche Schnittstelle. Die Klasse, die es implementiert, sagt: „Ich implementieren eine Funktion zum Vergleichen von Objekten.“ Das Einzige, woran man sich wirklich erinnern sollte, ist der Vergleichsvertrag, der wie folgt lautet:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый ein Objekt отрицательный, то есть меньше)
  • положительный int (первый ein Objekt положительный, хороший, то есть больший)
  • ноль = ein Objektы равны
Schreiben wir nun einen Komparator. Wir müssen java.util.Comparator importieren . Fügen Sie nach dem Import eine Methode zu main hinzu: Comparator<Message> comparator = new Comparator<Message>(); Dies wird natürlich nicht funktionieren, weil Komparator ist eine Schnittstelle. Daher werden wir nach den Klammern geschweifte Klammern hinzufügen { }. In diese Klammern schreiben wir die Methode:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Sie müssen nicht einmal daran denken, dies zu schreiben. Ein Komparator ist jemand, der einen Vergleich durchführt, also einen Vergleich durchführt. Um die Frage zu beantworten, in welcher Reihenfolge die verglichenen Objekte vorliegen, geben wir int zurück. Das ist eigentlich alles. Einfach und unkompliziert. Wie wir aus dem Beispiel sehen können, gibt es neben Comparator eine weitere Schnittstelle – java.lang.Comparable , deren Implementierung wir durch die Methode „compareTo“ definieren müssen . Diese Schnittstelle besagt: „Eine Klasse, die eine Schnittstelle implementiert, ermöglicht den Vergleich von Instanzen der Klasse.“ Die Implementierung von „compareTo“ durch Integer sieht beispielsweise so aus:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Wie kann man sich all diese Schnittstellen merken? Und warum? Alles kommt aus dem Englischen. Vergleichen – um zu vergleichen, ist derjenige, der vergleicht, Comparator (z. B. als Registrar. Das heißt, derjenige, der sich registriert), und das Adjektiv „verglichen“ ist Comparable. Nun, „Vergleichen mit“ wird nicht nur mit vergleichen mit, sondern auch mit vergleichen mit übersetzt. Es ist einfach. Die Java-Sprache wurde von englischsprachigen Menschen geschrieben, und bei der Benennung aller Dinge in Java ließen sie sich einfach vom Englischen leiten und es gab eine Art Logik in der Benennung. Und die Methode „compareTo“ beschreibt, wie eine Instanz einer Klasse mit anderen Instanzen verglichen werden soll. Beispielsweise werden Zeichenfolgen lexigrafisch und Zahlen anhand ihres Werts verglichen.
Komparator in Java - 2
Java 8 brachte einige nette Änderungen. Wenn wir uns die Comparator-Schnittstelle genau ansehen, werden wir feststellen, dass sich darüber eine Anmerkung befindet @FunctionalInterface. Tatsächlich dient diese Anmerkung der Information und bedeutet, dass diese Schnittstelle funktionsfähig ist. Das bedeutet, dass diese Schnittstelle nur eine abstrakte Methode ohne Implementierung hat. Was bringt uns das? Wir können den Komparatorcode jetzt wie folgt schreiben:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
In Klammern wird angegeben, wie wir die Variablen benennen. Java selbst wird das sehen, weil ... Wenn es nur eine Methode gibt, ist klar, welche Eingabeparameter wie viele und welche Typen benötigt werden. Als nächstes sagen wir mit einem Pfeil, dass wir sie in diesen Abschnitt des Codes übertragen wollen. Darüber hinaus sind dank Java 8 Standardmethoden in Schnittstellen aufgetaucht – das sind Methoden, die standardmäßig (standardmäßig) angezeigt werden, wenn wir eine Schnittstelle implementieren. In der Comparator-Schnittstelle gibt es mehrere davon. Zum Beispiel:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Es gibt eine andere Methode, die Ihren Code sauberer macht. Schauen wir uns das obige Beispiel an, in dem wir unseren Komparator beschrieben haben. Was macht er? Es ist ziemlich primitiv. Es nimmt einfach ein Objekt und extrahiert daraus einen vergleichbaren Wert. Beispielsweise implementiert Integer einen Vergleich, sodass wir „compareTo“ für Nachrichten-ID-Werte durchführen konnten. Diese einfache Komparatorfunktion kann auch so geschrieben werden:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Das heißt wörtlich: „Wir haben einen Komparator, der wie folgt vergleicht: Er nimmt Objekte, ruft daraus mit der Methode getId() Comparable ab und vergleicht mit CompareTo.“ Und keine schrecklichen Designs mehr. Und zum Schluss möchte ich noch eine Besonderheit erwähnen. Komparatoren können miteinander verkettet werden. Zum Beispiel:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Anwendung

Die Komparator-Deklaration erwies sich doch als recht logisch, nicht wahr? Jetzt müssen wir sehen, wie und an welchen Orten wir es verwenden. → Collections.sort (java.util.Collections) Natürlich können wir Sammlungen auf diese Weise sortieren. Aber nicht alles, nur Listen. Und hier gibt es nichts Ungewöhnliches, denn... Es ist die Liste, die den Zugriff auf ein Element über den Index erfordert. Und dadurch kann Element Nummer zwei mit Element Nummer drei ausgetauscht werden. Daher ist diese Sortierung nur für Listen möglich:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) Arrays lassen sich auch bequem sortieren. Auch hier aus dem gleichen Grund wie beim Zugriff auf Elemente über den Index. → Nachkommen von java.util.SortedSet und java.util.SortedMap Wie wir uns erinnern, garantieren Set und Map nicht die Reihenfolge der Speicherung von Datensätzen. ABER wir haben spezielle Implementierungen, die Ordnung garantieren. Und wenn die Sammlungselemente java.lang.Comparable nicht implementieren, können wir Comparator an den Konstruktor solcher Sammlungen übergeben:
Set<Message> msgSet = new TreeSet(comparator);
Stream-API In der Stream-API, die in Java 8 erschien, ermöglicht ein Komparator die Vereinfachung der Arbeit an Stream-Elementen. Wir benötigen zum Beispiel 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 aufhören, aber es gibt noch interessantere Probleme. Sie müssen beispielsweise eine Karte vorbereiten, deren Schlüssel die Nachrichten-ID ist. Gleichzeitig möchten wir diese Schlüssel so sortieren, dass die Schlüssel in der Reihenfolge vom kleinsten zum größten sortiert sind. Beginnen wir mit diesem Code:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Was wir hier zurückbekommen, ist tatsächlich eine HashMap. Und wie wir wissen, garantiert es keine Bestellung. Daher waren unsere nach ID sortierten Datensätze einfach nicht mehr in Ordnung. Nicht gut. Wir müssen unseren Sammler ein wenig ä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 sah etwas gruseliger aus, aber das Problem wurde dank der expliziten Implementierung der TreeMap nun korrekt gelöst. Mehr über die verschiedenen Gruppen können Sie hier lesen: Sie können den Sammler selbst erstellen. Weitere Informationen finden Sie hier: „Erstellen eines benutzerdefinierten Kollektors in Java 8“ . Und es ist nützlich, die Diskussion hier zu lesen: „Java 8-Liste zum Zuordnen mit Stream“ .
Komparator in Java - 3
Comparator und Comparable Rake sind gut. Aber mit ihnen ist eine Nuance verbunden, die es wert ist, beachtet zu werden. Wenn eine Klasse eine Sortierung durchführt, berechnet sie, dass sie Ihre Klasse in Comparable umwandeln kann. Ist dies nicht der Fall, erhalten Sie zur Ausführungszeit 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 in unserem Beispiel stürzt es tatsächlich mit der Fehlermeldung ab: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Und das alles nur, weil versucht wurde, die Elemente zu sortieren (es ist schließlich ein SortedSet). Und ich konnte es nicht. Dies sollten Sie bei der Arbeit mit SortedMap und SortedSet beachten. Zusätzlich zum Anschauen empfohlen: Yuri Tkach: HashSet and TreeSet – Collections #1 – Advanced Java
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION