Search code examples
javalambdacollectionscomparator

What is the use of Comparator.comparing in respect to Comparator


As far as I understand Comparator is a functional interface used to compare 2 objects with int compare(T o1, T o2) as the abstract function that takes two argument. but there is also a function Comparator.comparing(s->s) that can take a lambda function with only one input parameter. for Example to sort a Collection using streams

        List<String> projects=Arrays.asList("abc","def","sss","aaa","bbb");
        projects.stream().sorted((x,y)->y.compareTo(x)).forEach(s->System.out.println(s));
        projects.stream().sorted(Comparator.comparing(s->s)).forEach(s->System.out.println(s));

the sorted method takes a Comparator as a argument. So I am able to understand the first lambda expression but I wonder the use of Comparator.comparing(s->s) i.e. is Comparator.comparing() used for converting a single argument lambda expression to a double argument one or does it has some other use as well. Also please explain the part of the below function declaration.

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)

Solution

  • is Comparator.comparing() used for converting a single argument lambda expression to a double argument?

    Yes, you can sort of think of it like that.

    When sorting things, you are supposed to specify "given two things a and b, which of them is greater, or are they equal?" using a Comparator<T>. The a and b is why it has 2 lambda parameters, and you return an integer indicating your answer to that question.

    However, a much more convenient way to do this is to specify "given a thing x, what part of x do you want to sort by?". And that is what you can do with the keyExtractor argument of Comparator.comparing.

    Compare:

    /*
    given two people, a and b, the comparison result between a and b is the 
    comparison result between a's name and b's name
    */
    Comparator<Person> personNameComparator = 
        (a, b) -> a.getName().compareTo(b.getName());
    
    /*
    given a person x, compare their name
    */
    Comparator<Person> personNameComparator = 
        Comparator.comparing(x -> x.getName()); // or Person::getName
    

    The latter is clearly much more concise and intuitive. We tend to think about what things to sort by, rather than how exactly to compare two things, and the exact number to return depending on the comparison result.

    As for the declaration for comparing:

    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
                Function<? super T, ? extends U> keyExtractor)
    

    The <T, U extends Comparable<? super U>> part first declares two generic type parameters - T is what the comparator compares (Person in the above case), and U is the type that you are actually comparing (String in the above case), hence it extends Comparable.

    keyExtractor is the parameter you pass in, such as x -> x.getName(), that should answer the question of "when given a T, what is a U that you want to compare by?".

    If you are confused by the ? super and ? extends, read What is PECS?.

    If you haven't realised already, the implementation of comparing basically boils down to:

    return (a, b) -> keyExtractor.apply(a).compareTo(keyExtractor.apply(b));