Comparator.reversed () não compila usando lambda

111

Tenho uma lista com alguns objetos de usuário e estou tentando classificar a lista, mas só funciona usando a referência de método, com a expressão lambda que o compilador dá um erro:

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

Erro:

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
fonte

Respostas:

145

Esta é uma fraqueza no mecanismo de inferência de tipo do compilador. Para inferir o tipo de uno lambda, o tipo de destino para o lambda precisa ser estabelecido. Isso é feito da seguinte maneira. userList.sort()está esperando um argumento do tipo Comparator<User>. Na primeira linha, Comparator.comparing()precisa retornar Comparator<User>. Isso implica que Comparator.comparing()precisa de Functionum Userargumento. Assim no lambda da primeira linha, udeve ser do tipo Usere tudo funciona.

Na segunda e terceira linhas, a digitação do destino é interrompida pela presença da chamada para reversed(). Não tenho certeza do porquê; tanto o receptor quanto o tipo de retorno reversed()são, Comparator<T>então parece que o tipo de destino deveria ser propagado de volta para o receptor, mas não é. (Como eu disse, é uma fraqueza.)

Na segunda linha, a referência do método fornece informações de tipo adicionais que preenchem essa lacuna. Essas informações estão ausentes na terceira linha, portanto, o compilador infere user Object(o fallback de inferência de último recurso), o que falha.

Obviamente, se você pode usar uma referência de método, faça isso e funcionará. Às vezes, você não pode usar uma referência de método, por exemplo, se quiser passar um parâmetro adicional, então você tem que usar uma expressão lambda. Nesse caso, você forneceria um tipo de parâmetro explícito no lambda:

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

Pode ser possível que o compilador seja aprimorado para cobrir esse caso em uma versão futura.

Stuart Marks
fonte
28
Lambdas são divididos em digitados implicitamente (sem tipos de manifesto para parâmetros) e digitados explicitamente ; as referências de método são divididas em exatas (sem sobrecargas) e inexatas . Quando uma chamada de método genérico em uma posição de receptor tem argumentos lambda, e os parâmetros de tipo não podem ser totalmente inferidos dos outros argumentos, você precisa fornecer um lambda explícito, um método ref exato, um cast de tipo de destino ou testemunhas de tipo explícito para a chamada de método genérico para fornecer as informações de tipo adicionais necessárias para prosseguir.
Brian Goetz
1
@StuartMarks, você "não tem certeza porque" o compilador está agindo assim. Mas o que diz a especificação da linguagem ? Deve haver informações suficientes para determinar os tipos genéricos, de acordo com a especificação do idioma? Nesse caso, este é um bug do compilador e deve ser arquivado e tratado de acordo. Caso contrário, é uma área na qual a linguagem Java deve ser aprimorada. Qual é?
Garret Wilson
8
Acho que podemos tratar os comentários de Brian como definitivos, já que ele escreveu a especificação em questão :-)
minimalis
1
Infelizmente, nada disso explica por que ele está funcionando sem reverso enquanto não está funcionando com reverso.
Chris311
90

Você pode contornar essa limitação usando os dois argumentos Comparator.comparingcom Comparator.reverseOrder()o segundo argumento:

users.sort(comparing(User::getName, reverseOrder()));
Misha
fonte
4
Agradável. Gosto mais disso do que usar um lambda digitado explicitamente. Ou, melhor ainda users.sort(reverseOrder(comparing(User::getName)));,.
termina em
10
Observe que o reverseOrder(Comparator<T>)método acima é in java.util.Collections, não in Comparator.
termina em