Ich habe den Unterschied zwischen Collections.sort
und untersucht list.sort
, insbesondere in Bezug auf die Verwendung der Comparator
statischen Methoden und ob Parametertypen in den Lambda-Ausdrücken erforderlich sind. Bevor wir beginnen, weiß ich, dass ich Methodenreferenzen verwenden könnte, z. B. Song::getTitle
um meine Probleme zu lösen, aber meine Abfrage hier ist nicht so sehr etwas, das ich beheben möchte, sondern etwas, auf das ich eine Antwort haben möchte, dh warum behandelt der Java-Compiler dies so .
Das sind meine Erkenntnisse. Angenommen, wir haben einen ArrayList
Typ Song
, bei dem einige Songs hinzugefügt wurden. Es gibt drei Standard-Get-Methoden:
ArrayList<Song> playlist1 = new ArrayList<Song>();
//add some new Song objects
playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") );
playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") );
playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") );
Hier ist ein Aufruf beider Arten von Sortiermethoden, die funktionieren, kein Problem:
Collections.sort(playlist1,
Comparator.comparing(p1 -> p1.getTitle()));
playlist1.sort(
Comparator.comparing(p1 -> p1.getTitle()));
Sobald ich anfange zu verketten thenComparing
, passiert Folgendes:
Collections.sort(playlist1,
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
playlist1.sort(
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
dh Syntaxfehler, weil es den Typ von p1
nicht mehr kennt . Um dies zu beheben, füge ich den Typ Song
zum ersten Parameter (zum Vergleichen) hinzu:
Collections.sort(playlist1,
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
playlist1.sort(
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
Jetzt kommt hier der verwirrende Teil. Für p laylist1.sort
, dh die Liste, werden alle Kompilierungsfehler für beide folgenden thenComparing
Aufrufe behoben. Doch für Collections.sort
, sie löst es für die erste, aber nicht das letzte. Ich habe getestet, dass mehrere zusätzliche Aufrufe hinzugefügt wurden, thenComparing
und es wird immer ein Fehler für den letzten angezeigt, es sei denn, ich habe (Song p1)
den Parameter eingegeben.
Nun habe ich dies weiter getestet, indem ich ein erstellt TreeSet
und Folgendes verwendet habe Objects.compare
:
int x = Objects.compare(t1, t2,
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
Set<Song> set = new TreeSet<Song>(
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
Es passiert dasselbe wie in, denn TreeSet
es gibt keine Kompilierungsfehler, aber Objects.compare
beim letzten Aufruf thenComparing
wird ein Fehler angezeigt.
Kann jemand bitte erklären, warum dies geschieht und warum es überhaupt nicht nötig ist, es zu verwenden, (Song p1)
wenn einfach die Vergleichsmethode aufgerufen wird (ohne weitere thenComparing
Aufrufe).
Eine andere Abfrage zum gleichen Thema ist, wenn ich dies tue TreeSet
:
Set<Song> set = new TreeSet<Song>(
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
dh den Typ Song
aus dem ersten Lambda-Parameter für den Aufruf der Vergleichsmethode entfernen , es werden Syntaxfehler unter dem Aufruf zum Vergleichen und dem ersten Aufruf zum, thenComparing
aber nicht zum letzten Aufruf von angezeigt thenComparing
- fast das Gegenteil von dem, was oben geschah! Während für alle die anderen drei Beispiele , dh mit Objects.compare
, List.sort
und Collections.sort
wenn ich entfernen , die erste Song
param Typ es zeigt Syntaxfehler für alle Anrufe.
Vielen Dank im Voraus.
Bearbeitet, um einen Screenshot von Fehlern aufzunehmen, die ich in Eclipse Kepler SR2 erhalten habe. Die ich seitdem gefunden habe, sind Eclipse-spezifisch, da sie beim Kompilieren mit dem JDK8-Java-Compiler in der Befehlszeile OK kompiliert werden.
t1
undt2
imObjects.compare
Beispiel? Ich versuche, sie abzuleiten, aber es ist unlösbar, meine Typinferenz über die Typinferenz des Compilers zu legen. :-)Antworten:
Erstens werden alle Beispiele, von denen Sie sagen, dass sie Fehler verursachen, mit der Referenzimplementierung (javac aus JDK 8) gut kompiliert. Sie funktionieren auch in IntelliJ einwandfrei, sodass die angezeigten Fehler möglicherweise Eclipse-spezifisch sind.
Ihre zugrunde liegende Frage scheint zu sein: "Warum funktioniert es nicht mehr, wenn ich anfange zu verketten?" Der Grund hierfür ist, während Lambda - Ausdrücke und generische Methode Anrufungen sind Poly Ausdrücke (ihre Art ist kontextsensitiv) , wenn sie als Methodenparameter angezeigt werden , wenn sie stattdessen als Methode Empfänger Ausdrücke erscheinen, sind sie nicht.
Wenn du sagst
Es gibt genügend Typinformationen, um sowohl das Typargument
comparing()
als auch den Argumenttyp zu lösenp1
. Dercomparing()
Aufruf erhält seinen Zieltyp aus der Signatur vonCollections.sort
, daher ist bekannt, dass er a zurückgebencomparing()
mussComparator<Song>
und daher seinp1
mussSong
.Aber wenn Sie anfangen zu verketten:
Jetzt haben wir ein Problem. Wir wissen, dass der zusammengesetzte Ausdruck
comparing(...).thenComparing(...)
einen Zieltyp von hatComparator<Song>
, aber da der Empfängerausdruck für die Kettecomparing(p -> p.getTitle())
ein generischer Methodenaufruf ist und wir seine Typparameter nicht aus den anderen Argumenten ableiten können, haben wir Pech . Da wir den Typ dieses Ausdrucks nicht kennen, wissen wir nicht, dass er einethenComparing
Methode usw. hat.Es gibt verschiedene Möglichkeiten, dies zu beheben. Alle umfassen das Einfügen weiterer Typinformationen, damit das ursprüngliche Objekt in der Kette ordnungsgemäß eingegeben werden kann. Hier sind sie in grober Reihenfolge abnehmender Begehrlichkeit und zunehmender Eindringlichkeit:
Song::getTitle
. Dies gibt dann genügend Typinformationen, um auf die Typvariablen für dencomparing()
Aufruf zu schließen , und gibt ihm daher einen Typ, und setzt daher die Kette fort.comparing()
Anruf an :Comparator.<Song, String>comparing(...)
.Comparator<Song>
.quelle
Comparator.<Song, String>comparing(...)
.Das Problem ist die Typinferenz. Ohne
(Song s)
dem ersten Vergleich ein hinzuzufügen ,comparator.comparing
kennt der Typ der Eingabe nicht, daher wird standardmäßig Object verwendet.Sie können dieses Problem auf eine von drei Arten beheben:
Verwenden Sie die neue Java 8-Methodenreferenzsyntax
Ziehen Sie jeden Vergleichsschritt in eine lokale Referenz
BEARBEITEN
Erzwingen des vom Komparator zurückgegebenen Typs (beachten Sie, dass Sie sowohl den Eingabetyp als auch den Vergleichsschlüsseltyp benötigen)
Ich denke, der "letzte"
thenComparing
Syntaxfehler führt Sie in die Irre. Es ist eigentlich ein Typproblem mit der gesamten Kette, es ist nur der Compiler, der nur das Ende der Kette als Syntaxfehler markiert, da dann der endgültige Rückgabetyp vermutlich nicht übereinstimmt.Ich bin mir nicht sicher, warum
List
ein besserer Inferenzjob ausgeführt wird, alsCollection
da er denselben Erfassungstyp ausführen sollte, aber anscheinend nicht.quelle
ArrayList
aber nicht für dieCollections
Lösung (wenn der erste Aufruf in der Kette einenSong
Parameter hat)?Eine andere Möglichkeit, mit diesem Fehler bei der Kompilierung umzugehen:
Setzen Sie die Variable Ihrer ersten Vergleichsfunktion explizit um und los geht's. Ich habe die Liste der org.bson.Documents-Objekte sortiert. Bitte schauen Sie sich den Beispielcode an
Comparator<Document> comparator = Comparator.comparing((Document hist) -> (String) hist.get("orderLineStatus"), reverseOrder()) .thenComparing(hist -> (Date) hist.get("promisedShipDate")) .thenComparing(hist -> (Date) hist.get("lastShipDate")); list = list.stream().sorted(comparator).collect(Collectors.toList());
quelle
playlist1.sort(...)
Erstellt eine Song-Grenze für die Typvariable E aus der Deklaration von Playlist1, die den Komparator "kräuselt".In
Collections.sort(...)
gibt es keine solche Grenze, und die Schlussfolgerung aus dem Typ des ersten Komparators reicht nicht aus, damit der Compiler auf den Rest schließen kann.Ich denke, Sie würden "korrektes" Verhalten erhalten
Collections.<Song>sort(...)
, aber Sie müssen kein Java 8 installieren, um es für Sie zu testen.quelle