• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

The comparing() method of a Comparator: How does a comparison produce a Comparator?

 
Ranch Hand
Posts: 353
3
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
The comparing() method of a Comparator

Accepts a function that extracts a sort key from a type T, and returns a Comparator<T> that compares by that sort key using the specified Comparator.
The returned comparator is serializable if the specified function and comparator are both serializable.



I regard the Comparator.comparing() method as a little processing plant where two objects of the same type are compared to know whether they are equal or whether one object is greater than or less than the other.  The processing that is done inside this little plant depends on the input supplied to it, but ultimately, two objects of the same class are compared.
Now, the comparison between c1 and c2 is suppoesed to result in one of three possible outcomes:- c1 is less than c2, or c1 is equal to c2 or c1 is greater than c2.

The Lambda expression (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2)); performs the comparison and returns an integer value.  Apology for the following newbie questions:
1. At what point does the integer value returned by the Lambda expression become a Compartor?
2. Does an integer value become a Comparator just by prepending (Comparator<T> & Serializable) to it?
3. Inside the body of the comparing() method, which line of code produces the Comparator?

I know that I have completely misunderstood the concept, yes.  That is exactly why I am asking these questions. My desire is to have a clear understanding of the underlying principles.  
 
Saloon Keeper
Posts: 15491
363
  • Likes 3
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Biniman Idugboe wrote:1. At what point does the integer value returned by the Lambda expression become a Compartor?


It doesn't. The lambda expression returns a Comparator. The body of the lambda expression isn't executed until the client calls the compare() method.

2. Does an integer value become a Comparator just by prepending (Comparator<T> & Serializable) to it?


No. The compiler sees that the method is supposed to return a Comparator, so when you use a lambda expression, the compiler knows that it has to return an object that implements the Comparator interface using the body of the lambda expression as the body of the compare() method. The int doesn't get turned into a Comparator, the lambda expression simply returns an object that implements Comparator, and Comparator.compare() returns an int.

3. Inside the body of the comparing() method, which line of code produces the Comparator?


None. The entire lambda expression returns a Comparator. The body of the lambda expression is just the implementation of that Comparator.

You have to think of the -> symbol as an actual operator. For instance, where + can accept two ints and return another int, -> can accept a parameter list and a method body, and return an object that implements some interface.
 
Marshal
Posts: 79153
377
  • Likes 2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
So, is that the code from src.zip? I am sure they didn't indent it as you did, with those long lines. Since it has the -> operator in, it must be the Java8 version, and that particular method was only introduced in Java8.

How much do you know about λ expressions? Let's get that λ back to an anonymous class. It tells you what sort of object it returns, a Comparator<T>, and we know that Comparator is a FunctionalInterface; it even says so in the documentation. It is easier to see if you
  • 1: Read the Functional Interface link above and this section of the Java® Language Specification (=JLS).
  • 2: Read the Java7 version of the documentation.
  • You can see there is the equals() method, which it tells you can safely be ignored, and the compare() method. So we know we are going to have to create a class of that name and a method of that name. Let's begin:-Let's omit the documentation comments, otherwise no changes yet:-Let's make things simpler: let's forget what the documentation says about

    Note: It is generally a good idea for comparators to also implement java.io.Serializable . . .

    Let's forget about lines 5‑6 which simply prevent nulls being passed.Now, we have a common or garden λ, which I shall turn into an anonymous class. What precedes the -> is the parameters for a method and what follows it is the body of the method, and we know the method signature from hereNow, let's apply a name to the class.Now, we don't know what keyExtractor and keyComparator are. Maybe you have a Person class with String lastName. Maybe keyExtractor simply gets the lastName field. Maybe then keyComparator is a Comparator<String> which can compare Strings. You now have something similar to this λ:-As you know, Strings already implement Comparable<String>, so you can call compareTo on them.

    In that case, look on T as meaning Person; your keyExtractor gets a String (=U) out of it and the keyComparator<‍U> compares them.In that example, which does the same thing, the bit with p -> in gets lastName fields, and the bit starting (s1, s2) -> (line 3) compares two Strings with their compareTo method.
     
    Biniman Idugboe
    Ranch Hand
    Posts: 353
    3
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    It took me time to get back here.  I tried to follow the links.  Quite a lot to take in.  Many of the concepts are just simply elusive.  Now, I know that Lambda expressions enable programmers to treat functionality as a method argument, to treat code as data, to pass an implementation of a functional interface as an argument to a method. Sounds really cool!  But I am still not quite there.
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 15491
    363
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    You will, in time :)
     
    Campbell Ritchie
    Marshal
    Posts: 79153
    377
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Start with something simple. Predict what this code will print:-Read about the methods: IntStream#range(10, 15) map() boxed() Stream#collect() and Collectors#toList(). Note that the descriptions of the classes have more useful information in. Read about IntUnaryOperator and go to this Java™ Tutorials page about λs and see how you can use a λ to create an IntUnaryOperator instance.
     
    Biniman Idugboe
    Ranch Hand
    Posts: 353
    3
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    So, the lambda expression is equivalent to something like:

    The range of integers is 10, 11, 12, 13, 14.
    The anonymous method is invoked on each of the integers and the resulting list will contain 20, 22, 24, 26, 28.
    Basically, the anonymous method was passed as an argument to the map() method.
     
    Biniman Idugboe
    Ranch Hand
    Posts: 353
    3
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Because the map() takes a type IntUnaryOperator, the lambda expression returns a IntUnaryOperator.  The equivalent anonymous class could be something like:

    The functional interface is passed as though it were an argument, to the map() method which applies it to every element in the stream of integer values. Right?
     
    Biniman Idugboe
    Ranch Hand
    Posts: 353
    3
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Oops!
     
    Campbell Ritchie
    Marshal
    Posts: 79153
    377
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
         
     
    Campbell Ritchie
    Marshal
    Posts: 79153
    377
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Don't say anonymous method, say λ expression (or lambda expression).
    Now you have understood a simple example, try writing a slightly more difficult one. What about turning those ints to Strings and joining them to print [20, 22, 24, 26, 28]?Go through the methods for IntStream and see what you can find to map an int to a String.
     
    Biniman Idugboe
    Ranch Hand
    Posts: 353
    3
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Quite honestly, I do not understand the details of how these things work.  I am certaining missing the fundamentals.  The documentation is even more difficult to read and understand. What is the best way to approach this seemingly bewildering language?

    Anyway, I tried working on the given task for several hours.

    .flatMap(i -> Integer.toUnsignedString(i))
    Reason: argument mismatch; bad return type in lambda expression
         String cannot be converted to IntStream

    .flatMap(i -> Integer.toString(i))
    error: incompatible types: cannot infer type-variable(s) R

    .flatMap(i -> toString(i))
    error: method toString in class Object cannot be applied to given types;

    .forEach(i -> i.toString())
     error: int cannot be dereferenced


    By trial and error, I came up with the following two snippets. No need to ask me how the Object::String works. I do not know for now. I only just came upon the syntax in the documentation. My worry?  I hate doing things by trial and error.

     
    Campbell Ritchie
    Marshal
    Posts: 79153
    377
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Yes, that will work, but you now have two λs, that in lines 10 and 27 being hidden behind a method reference. You have obviously put lots of work into this Well done.
    That method reference is like this λ: i -> i.toString() You could probably have written Integer::toString instead.
    I can think of another way to do it:-
    String::valueOf, which is like writing i -> String.valueOf(i)

    I was thinking of the mapToObj method: .mapToObj(i -> String.valueOf(i)) which can be shortened to .mapToObj(String::valueOf) You can read about the syntax String::valueOf in the Java™ Tutorials. No, it isn't easy first time, but you are doing a lot better than you thought yesterday.

    The flatMap method won't help; it is there for converting a stream of arrays to a single stream.We now have four different ways to change the ints to Strings, which goes to show there is often no one single “right” answer.
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 15491
    363
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Then you should reason about it. In Campbell's code snippet, Collectors.joining() transforms a stream of char sequences to a string. So if the output is "[20, 22, 24, 26, 28]", the input must be { "20", "22", "24", "26", "28" }. You have an IntStream, so somehow you must transform { 20, 22, 24, 26, 28 } to { "20", "22", "24", "26", "28" }. Transform! That should immediately ring a bell to use some variant of the map() function. We need to map ints to Strings. However, ints are primitives and Strings are reference types. You can solve this by first calling boxed(), like you did, but IntStream has another method: mapToObj(). mapToObj() requires an IntFunction. IntFunction has one method that takes an int, and returns some reference type of your choice. We want to convert an int to a String, so let's start with a lambda:
    This works, but there is a shorter way of writing this. We just declared a variable and passed it to a function, but we can just tell the mapToObj() method to use the function directly, without declaring the variables. This is also known as using a method reference:

    It takes some practice to get used to this, but it starts by realizing that mapToObj() requires a function that takes an int and returns an object, and that Integer.toString() IS a function that takes an int and returns an object, so you can use it without writing a lambda expression.
     
    Biniman Idugboe
    Ranch Hand
    Posts: 353
    3
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Thanks for all the encouragements.
     
    Campbell Ritchie
    Marshal
    Posts: 79153
    377
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    That's a pleasure and well done putting all that effort into working out your solutions.
    reply
      Bookmark Topic Watch Topic
    • New Topic