Comparator.reversed () wird nicht mit Lambda kompiliert

111

Ich habe eine Liste mit einigen Benutzerobjekten und ich versuche, die Liste zu sortieren, aber es funktioniert nur unter Verwendung der Methodenreferenz. Mit dem Lambda-Ausdruck gibt der Compiler einen Fehler aus:

List<User> userList = Arrays.asList(u1, u2, u3);
userList.sort(Comparator.comparing(u -> u.getName())); // works
userList.sort(Comparator.comparing(User::getName).reversed()); // works
userList.sort(Comparator.comparing(u -> u.getName()).reversed()); // Compiler error

Error:

com\java8\collectionapi\CollectionTest.java:35: error: cannot find symbol
            userList.sort(Comparator.comparing(u -> u.getName()).reversed());
                                                     ^
symbol:   method getName()
location: variable u of type Object
1 error
Andrey
quelle

Antworten:

145

Dies ist eine Schwachstelle im Typinferenzmechanismus des Compilers. Um auf den uLambda- Typ schließen zu können , muss der Zieltyp für das Lambda festgelegt werden. Dies wird wie folgt erreicht. userList.sort()erwartet ein Argument vom Typ Comparator<User>. In der ersten Zeile Comparator.comparing()muss zurückkehren Comparator<User>. Dies bedeutet , dass Comparator.comparing()muss ein , Functiondie eine nimmt UserArgument. Somit muss im Lambda in der ersten Zeile uvom Typ seinUser und alles funktioniert.

In der zweiten und dritten Zeile wird die Zieleingabe durch das Vorhandensein des Anrufs an unterbrochen reversed(). Ich bin mir nicht ganz sicher warum; Sowohl der Empfänger als auch der Rückgabetyp von reversed()sind Comparator<T>so, dass es so aussieht, als ob der Zieltyp an den Empfänger zurückgegeben werden sollte, aber dies ist nicht der Fall. (Wie ich schon sagte, es ist eine Schwäche.)

In der zweiten Zeile enthält die Methodenreferenz zusätzliche Typinformationen, die diese Lücke füllen. Diese Informationen fehlen in der dritten Zeile, sodass der Compiler darauf schließen ukann Object(der Inferenz-Fallback des letzten Auswegs), was fehlschlägt.

Wenn Sie eine Methodenreferenz verwenden können, tun Sie dies natürlich und es wird funktionieren. Manchmal können Sie keine Methodenreferenz verwenden, z. B. wenn Sie einen zusätzlichen Parameter übergeben möchten, sodass Sie einen Lambda-Ausdruck verwenden müssen. In diesem Fall würden Sie im Lambda einen expliziten Parametertyp angeben:

userList.sort(Comparator.comparing((User u) -> u.getName()).reversed());

Möglicherweise kann der Compiler erweitert werden, um diesen Fall in einer zukünftigen Version abzudecken.

Stuart Marks
quelle
28
Lambdas werden in implizit typisierte (keine Manifesttypen für Parameter) und explizit typisierte unterteilt . Methodenreferenzen werden in exakte (keine Überladungen) und ungenaue unterteilt . Wenn ein generischer Methodenaufruf an einer Empfängerposition Lambda-Argumente enthält und die Typparameter nicht vollständig aus den anderen Argumenten abgeleitet werden können, müssen Sie entweder ein explizites Lambda, eine genaue Methodenreferenz, eine Zieltypumwandlung oder explizite Typzeugen für angeben Der generische Methodenaufruf, um die zusätzlichen Typinformationen bereitzustellen, die zum Fortfahren erforderlich sind.
Brian Goetz
1
@StuartMarks, Sie sind sich "nicht ganz sicher, warum" der Compiler so handelt. Aber was sagt die Sprachspezifikation aus ? Sollte es genügend Informationen geben, um die generischen Typen gemäß der Sprachspezifikation zu bestimmen? Wenn ja, ist dies ein Compiler-Fehler und sollte entsprechend abgelegt und behandelt werden. Ansonsten ist es ein Bereich, in dem die Java-Sprache verbessert werden sollte. Welches ist es?
Garret Wilson
8
Ich denke, wir können Brians Kommentare als endgültig behandeln, da er die fragliche Spezifikation geschrieben hat :-)
minimalis
1
Leider erklärt nichts davon, warum es ohne Umkehrung funktioniert, während es nicht mit Umkehrung funktioniert.
Chris311
90

Sie können diese Einschränkung umgehen, indem Sie das Zwei-Argument Comparator.comparingmit Comparator.reverseOrder()als zweites Argument verwenden:

users.sort(comparing(User::getName, reverseOrder()));
Mischa
quelle
4
Nett. Ich mag das besser als ein explizit typisiertes Lambda. Oder noch besser users.sort(reverseOrder(comparing(User::getName)));.
Rolve
10
Beachten Sie, dass die reverseOrder(Comparator<T>)obige Methode in java.util.Collections, nicht in ist Comparator.
Rolve