Reputation: 2585
I have a list with some User objects and i'm trying to sort the list, but only works using method reference, with lambda expression the compiler gives an 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
Upvotes: 158
Views: 39010
Reputation: 875
Actually, the following coe is compiled:
List<Employee> list = ss.stream()
.sorted(Comparator.comparing(Employee::getName).thenComparing(e -> e.getSurname()).thenComparing(e -> e.getSalary()))
.toList();
Here, first invokation is a method reference Employee::getName
. But if you replace method reference with lambda exdpression like e -> e.getName()
the code is not compiled anymore.
I still think that this is a bug in Java Compiler
Upvotes: 0
Reputation: 12305
Contrary to the accepted and upvoted answer for which bounty has been awarded, this doesn't really have anything to do with lambdas.
The following compiles:
Comparator<LocalDate> dateComparator = naturalOrder();
Comparator<LocalDate> reverseComparator = dateComparator.reversed();
while the following does not:
Comparator<LocalDate> reverseComparator = naturalOrder().reversed();
This is because the compiler's type inference mechanism isn't strong enough to take two steps at once: determine that the reversed()
method call needs type parameter LocalDate
and therefore also the naturalOrder()
method call will need the same type parameter.
There is a way to call methods and explicitly pass a type parameter. In simple cases it isn't necessary because it's inferred, but it can be done this way:
Comparator<LocalDate> reverseComparator = Comparator.<LocalDate>naturalOrder().reversed();
In the example given in the question, this would become:
userList.sort(Comparator.comparing<User, String>(u -> u.getName()).reversed());
But as shown in the currently accepted answer, anything that helps the compiler inferring type User
for the comparing
method call without taking extra steps will work, so in this case you can also specify the type of the lambda parameter explicitly or use a method reference User::getName
that also includes the type User
.
Upvotes: 18
Reputation: 1
The static method Collections.reverseOrder(Comparator<T>)
seems to be the most elegant solution that has been proposed. Just one caveat:
Comparator.reverseOrder()
requires that T implements comparable and relies on the natural sorting order.
Collections.reverseOrder(Comparator<T>)
has no restriction applied on type T
Upvotes: 0
Reputation: 28183
You can work around this limitation by using the two-argument Comparator.comparing
with Comparator.reverseOrder()
as the second argument:
users.sort(comparing(User::getName, reverseOrder()));
Upvotes: 119
Reputation: 132590
This is a weakness in the compiler's type inferencing mechanism. In order to infer the type of u
in the lambda, the target type for the lambda needs to be established. This is accomplished as follows. userList.sort()
is expecting an argument of type Comparator<User>
. In the first line, Comparator.comparing()
needs to return Comparator<User>
. This implies that Comparator.comparing()
needs a Function
that takes a User
argument. Thus in the lambda on the first line, u
must be of type User
and everything works.
In the second and third lines, the target typing is disrupted by the presence of the call to reversed()
. I'm not entirely sure why; both the receiver and the return type of reversed()
are Comparator<T>
so it seems like the target type should be propagated back to the receiver, but it isn't. (Like I said, it's a weakness.)
In the second line, the method reference provides additional type information that fills this gap. This information is absent from the third line, so the compiler infers u
to be Object
(the inference fallback of last resort), which fails.
Obviously if you can use a method reference, do that and it'll work. Sometimes you can't use a method reference, e.g., if you want to pass an additional parameter, so you have to use a lambda expression. In that case you'd provide an explicit parameter type in the lambda:
userList.sort(Comparator.comparing((User u) -> u.getName()).reversed());
It might be possible for the compiler to be enhanced to cover this case in a future release.
Upvotes: 217