Comparator.reversed () no se compila usando lambda

111

Tengo una lista con algunos objetos de usuario y estoy tratando de ordenar la lista, pero solo funciona usando la referencia de método, con la expresión lambda, el compilador da un error:

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
fuente

Respuestas:

145

Esta es una debilidad en el mecanismo de inferencia de tipos del compilador. Para inferir el tipo de uen la lambda, es necesario establecer el tipo de destino de la lambda. Esto se logra de la siguiente manera. userList.sort()espera un argumento de tipo Comparator<User>. En la primera línea, Comparator.comparing()debe regresar Comparator<User>. Esto implica que Comparator.comparing()necesita Functionun Userargumento. Por lo tanto, en la lambda de la primera línea, udebe ser de tipo Usery todo funciona.

En la segunda y tercera líneas, la escritura de destino se ve interrumpida por la presencia de la llamada a reversed(). No estoy completamente seguro de por qué; tanto el receptor como el tipo de retorno de reversed()son, Comparator<T>por lo que parece que el tipo de destino debería propagarse de regreso al receptor, pero no lo es. (Como dije, es una debilidad).

En la segunda línea, la referencia del método proporciona información de tipo adicional que llena este vacío. Esta información está ausente en la tercera línea, por lo que el compilador infiere uque es Object(la reserva de inferencia de último recurso), que falla.

Obviamente, si puede usar una referencia de método, hágalo y funcionará. A veces no puede usar una referencia de método, por ejemplo, si desea pasar un parámetro adicional, por lo que debe usar una expresión lambda. En ese caso, proporcionaría un tipo de parámetro explícito en el lambda:

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

Es posible que el compilador se mejore para cubrir este caso en una versión futura.

Stuart Marks
fuente
28
Las lambdas se dividen en tipadas implícitamente (sin tipos de manifiesto para parámetros) y tipadas explícitamente ; Las referencias de métodos se dividen en exactas (sin sobrecargas) e inexactas . Cuando una llamada de método genérico en una posición de receptor tiene argumentos lambda, y los parámetros de tipo no se pueden inferir completamente de los otros argumentos, debe proporcionar una lambda explícita, una referencia de método exacta, una conversión de tipo de destino o testigos de tipo explícitos para la llamada al método genérico para proporcionar la información de tipo adicional necesaria para continuar.
Brian Goetz
1
@StuartMarks, "no está completamente seguro de por qué" el compilador está actuando así. Pero, ¿qué dice la especificación del lenguaje ? ¿Debería haber suficiente información para determinar los tipos genéricos, según la especificación del lenguaje? Si es así, se trata de un error del compilador y debe archivarse y tratarse en consecuencia. De lo contrario, es un área en la que debería mejorarse el lenguaje Java. Cual es
Garret Wilson
8
Creo que podemos tratar los comentarios de Brian como definitivos, ya que escribió la especificación en cuestión :-)
minimalis
1
Lamentablemente, nada de esto explica por qué funciona sin revertir mientras no funciona con revertido.
Chris311
90

Puede solucionar esta limitación utilizando los dos argumentos Comparator.comparingcon Comparator.reverseOrder()como segundo argumento:

users.sort(comparing(User::getName, reverseOrder()));
Misha
fuente
4
Agradable. Me gusta más esto que usar una lambda escrita explícitamente. O, mejor aún, users.sort(reverseOrder(comparing(User::getName)));.
partir del
10
Tenga en cuenta que el reverseOrder(Comparator<T>)método anterior está en java.util.Collections, no en Comparator.
hasta el