How does method chaining work in Java 8 Comparator? [duplicate]
Asked Answered
D

2

10

I'm prepping up for the Java 8 certificate and the following has me puzzled a littlebit, maybe someone can help me with this? In the example, a Squirrel class is modelled. It has a name and a weight. Now you can make a Comparator class to sort this thing using both fields. So first sort by name and then by weight. Something like this:

public class ChainingComparator implements Comparator<Squirrel> {
    public int compare(Squirrel s1, Squirrel s2) {

        Comparator<Squirrel> c = Comparator.comparing(s -> s.getSpecies());
        c = c.thenComparingInt(s -> s.getWeight());

        return c.compare(s1, s2);
    }
}

So far so good.. but then the puzzling part. Underneath the code example, they state that you can write this in one single line by using method chaining. Maybe I misunderstand, but when I chain the comparing and the thenComparing parts, I get a compile error. It's got to do with the types of objects that are compared (first String, then int).

Why does it work when I put in an intermediate variable and not when chaining? And is it possible to chain at all?

Dapple answered 24/3, 2018 at 20:36 Comment(1)
try this one ` Comparator<Squirrel> c = Comparator.comparing((Squirrel s) -> s.getSpecies()).thenComparingInt(s -> s.getWeight());`Pasticcio
D
9

As you chain both, the compiler cannot infer the type argument of the returned comparator of comparing()because it depends on the returned comparator of thenComparingInt() that itself cannot be inferred.

Specify the type in the lambda parameter of comparing() (or use a method reference) and it solves the inference issue as the returned type of comparing() could so be inferred. :

    Comparator<Squirrel> c = Comparator.comparing((Squirrel s)  -> s.getSpecies())
                                       .thenComparingInt(s -> s.getWeight());

Note that specifying the type in the lambda parameter of thenComparingInt() (or using a method reference) such as :

    Comparator<Squirrel> c = Comparator.comparing(s -> s.getSpecies())
                                       .thenComparingInt((Squirrel s) -> s.getWeight());

will not work as the receiver (here the return type of the chained method) is not considered in the inference type computation.

This JDK 8 tutorial/documentation explains that very well :

Note: It is important to note that the inference algorithm uses only invocation arguments, target types, and possibly an obvious expected return type to infer types. The inference algorithm does not use results from later in the program.

Deckert answered 24/3, 2018 at 20:54 Comment(0)
S
6

Yes, it is possible - chain comparing(...) with thenComparing(...) and with compare(...) using method references instead of lambda expressions:

public int compare(Squirrel s1, Squirrel s2) {
    return Comparator.comparing(Squirrel::getSpecies)
        .thenComparing(Squirrel::getWeight)
        .compare(s1, s2);
}

Why does it work this way? I can't explain it better than Brian in his answer to a similar question.

Also, this can be rewritten with a 1 line (assuming you have a List of Squirrel that you want to sort):

list.sort(Comparator.comparing(Squirrel::getSpecies).thenComparing(Squirrel::getWeight));
Serena answered 24/3, 2018 at 20:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.