157

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
0

4 Answers 4

213
+200

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.

5
  • 40
    Lambdas are divided into implicitly-typed (no manifest types for parameters) and explicitly-typed; method references are divided into exact (no overloads) and inexact. When a generic method call in a receiver position has lambda arguments, and the type parameters cannot be fully inferred from the other arguments, you need to provide either an explicit lambda, an exact method ref, a target type cast, or explicit type witnesses for the generic method call to provide the additional type information needed to proceed. Commented Aug 7, 2014 at 16:02
  • 2
    @StuartMarks, you are "not entirely sure why" the compiler is acting like this. But what does the language specification say? Should there be sufficient information to determine the generic types, according to the language specification? If so, this is a compiler bug and should be filed and dealt with accordingly. Otherwise it is an area in which the Java language should be improved. Which is it? Commented May 11, 2017 at 19:44
  • 11
    I think we can treat Brian's comments as definitive, as he wrote the specification in question :-)
    – minimalis
    Commented Jun 27, 2017 at 21:19
  • 4
    Sadly none of this explains why it's working without reversed while it's not working with reversed.
    – Chris311
    Commented May 29, 2019 at 15:40
  • 1
    I was 100% sure this was a problem with the IDE because it didn't make sense that it works without the .reversed() but not with it. Commented Oct 23, 2020 at 20:50
118

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()));
3
  • 5
    Nice. I like this better than using an explicitly-typed lambda. Or, better yet, users.sort(reverseOrder(comparing(User::getName)));.
    – rolve
    Commented Nov 23, 2015 at 18:45
  • 12
    Note that the reverseOrder(Comparator<T>) method above is in java.util.Collections, not in Comparator.
    – rolve
    Commented Nov 23, 2015 at 18:55
  • Since question is about problem with lambda IMO it would be better to show that proposed solution works for lambdas like users.sort(comparing(u -> u.getName(), reverseOrder())); rather than method references (like it was in first version of this answer).
    – Pshemo
    Commented Feb 5, 2022 at 10:53
17

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.

2
  • 1
    Note that Comparator.comparing has two type parameters. For your example, assuming the type of getName is String, the correct invocation would be: userList.sort(Comparator<User, String>.comparing(u -> u.getName()).reversed());
    – snappieT
    Commented Apr 23, 2021 at 21:33
  • @snappieT Thanks, adapted the answer.
    – herman
    Commented Apr 26, 2021 at 10:30
0

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.