Comparing and Thencomparing Gives Compile Error

comparing and thenComparing gives compile error

Java needs to know a type of all variables. In many lambdas it can infer a type, but in your first code snippet, it cannot guess the type of s. I think the standard way to solve that problem would be to declare it explicitly:

    Comparator<String> c = Comparator.comparing((String s) -> s.split("\\s+")[0])
.thenComparingInt(s -> Integer.parseInt(s.split("\\s+")[1]));

If you look at this answer, it has a similar type declaration in the argument to Comparator.comparing().

Your method, explicitly giving the type arguments of comparing(), obviously works too.

For your other method, declaring two comparators, I am pretty confident that in this case Java can infer from the String on the left side of the assignment, just as in the conventional List <String> = new ArrayList<>();. When you go on to call thenComparing() in the same expression, Java can no longer see that the type from the left side is relevant. It would be a bit like int size = new ArrayList<>().size(); This works too:

    Comparator<String> name = Comparator.comparing(s -> s.split("\\s+")[0]);
Comparator<String> c = name.thenComparingInt(s -> Integer.parseInt(s.split("\\s+")[1]));

Very confused by Java 8 Comparator type inference

First, all the examples you say cause errors compile fine with the reference implementation (javac from JDK 8.) They also work fine in IntelliJ, so its quite possible the errors you're seeing are Eclipse-specific.

Your underlying question seems to be: "why does it stop working when I start chaining." The reason is, while lambda expressions and generic method invocations are poly expressions (their type is context-sensitive) when they appear as method parameters, when they appear instead as method receiver expressions, they are not.

When you say

Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));

there is enough type information to solve for both the type argument of comparing() and the argument type p1. The comparing() call gets its target type from the signature of Collections.sort, so it is known comparing() must return a Comparator<Song>, and therefore p1 must be Song.

But when you start chaining:

Collections.sort(playlist1,
comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist()));

now we've got a problem. We know that the compound expression comparing(...).thenComparing(...) has a target type of Comparator<Song>, but because the receiver expression for the chain, comparing(p -> p.getTitle()), is a generic method call, and we can't infer its type parameters from its other arguments, we're kind of out of luck. Since we don't know the type of this expression, we don't know that it has a thenComparing method, etc.

There are several ways to fix this, all of which involve injecting more type information so that the initial object in the chain can be properly typed. Here they are, in rough order of decreasing desirability and increasing intrusiveness:

  • Use an exact method reference (one with no overloads), like Song::getTitle. This then gives enough type information to infer the type variables for the comparing() call, and therefore give it a type, and therefore continue down the chain.
  • Use an explicit lambda (as you did in your example).
  • Provide a type witness for the comparing() call: Comparator.<Song, String>comparing(...).
  • Provide an explicit target type with a cast, by casting the receiver expression to Comparator<Song>.

Problems with sorting ListListString java?

Seems like java doesn't infer the type once you have a second layer.

In the first example.

data.sort( Comparator.comparing( e-> e.get(0) ) );

The type is inferred from data, but in the second example.

data.sort( Comparator.comparing( e-> e.get(0) ).reversed() );

The type of 'comparing' is not inferred. You can resolve this a couple ways, the easiest to be explicit.

data.sort( Comparator.comparing( (List<String> e) -> e.get(0) ).reversed() );

It seems funny that java doesn't chain inferences. If we include the intermediate step of creating the comparator, we can see pretty clearly it doesn't.

Comparator<List<String>> c = Comparator.comparing( e-> e.get(0) ).reversed();

| Error:
| cannot find symbol

| symbol: method get(int)

| Comparator<List> c = Comparator.comparing( e->e.get(0) ).reversed();

| ^---^

| Error:

| incompatible types: java.util.Comparator<java.lang.Object> cannot be converted to java.util.Comparator<java.util.List<java.lang.String>>

| Comparator<List> c = Comparator.comparing( e->e.get(0) ).reversed();

| ^--------------------------------------------^

Now we get two errors, one in the lambda because the argument is an Object, and the second error because we're creating a Comparator<Object>.

I'm thinking it works this way, but I'm not sure how to verify. 'reversed' will take the type argument of the instance calling it, the type is not inferred. By the time 'reversed' is called the object has to have been instantiated and the generic assigned. The call to comparing then has no upper bounds, it just has to return a Comparator<?>.

An alternative solution, specify the types when the Comparator is called.

Comparator<List<String>> c = Comparator.<List<String>, String>comparing( e->e.get(0) ).reversed();

Method Reference works for Comparator.comparing method whereas a lambda expression isn't working

The problem is with this expression:

Comparator.comparing(person -> person.getAge()).thenComparing(person -> person.getName())

In the first part Comparator.comparing(person -> person.getAge()), the compiler is not able to infer the type of the lambda argument as Person, because the expression is not assigned to a variable that helps infer that information.

On the other hand, when removing the thenComparing() part, the compiler can infer the type of the lambda argument, because the comparator Comparator.comparing(person -> person.getAge()) is targeted as argument to the sorted() method, which is being invoked on a Stream<Person>.

Note that if you explicitly specify the type of the argument, it works:

Comparator.comparing((Person person) -> person.getAge()).thenComparing(person -> person.getName())

How do I get Comparator.comparing to correctly infer type parameters?

You need to tell the compiler the type of the generic parameters.
Try this:

dtoPairs.sort(
Comparator.comparing(Pair<Long, NameDTO>::getLeft)
.reversed()
.thenComparing(Comparator.comparing(p -> p.getRight().getName())));

Comparator.reversed() does not compile using lambda

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.



Related Topics



Leave a reply



Submit