Java 8 Comparator comparing static function
Asked Answered
S

2

9

For the comparing source code in Comparator class

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
    Function<? super T, ? extends U> keyExtractor)
{
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

I understand the difference between super and extends. What i dont understand is that why this method have them. Can someone give me an example on what cannot be achieved when the parameter look like this Function<T, U> keyExtractor ?

For example :

Comparator<Employee> employeeNameComparator = Comparator.comparing(Employee::getName);

can also compile with the following function definition

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
    Function<T, U> keyExtractor)
{
  Objects.requireNonNull(keyExtractor);
  return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
Saltish answered 6/3, 2018 at 6:6 Comment(2)
As written this is borderline off-topic, asking for an example/recommendation. Maybe you could rephrase the question with some more context on what you're trying to achieve and exactly what is confusing, maybe examples of what you've tried that don't work?Allayne
Can you provides some more explanation about casting (Comparator<T> & Serializable) used on return ? What they mean ?Micro
C
15

Here is a simple example: comparing cars by weight. I will first describe the problem in text-form, and then demonstrate every possible way how it can go wrong if either ? extends or ? super is omitted. I also show the ugly partial workarounds that are available in every case. If you prefer code over prose, skip directly to the second part, it should be self-explanatory.


Informal discussion of the problem

First, the contravariant ? super T.

Suppose that you have two classes Car and PhysicalObject such that Car extends PhysicalObject. Now suppose that you have a function Weight that extends Function<PhysicalObject, Double>.

If the declaration were Function<T,U>, then you couldn't reuse the function Weight extends Function<PhysicalObject, Double> to compare two cars, because Function<PhysicalObject, Double> would not conform to Function<Car, Double>. But you obviously want to be able to compare cars by their weight. Therefore, the contravariant ? super T makes sense, so that Function<PhysicalObject, Double> conforms to Function<? super Car, Double>.


Now the covariant ? extends U declaration.

Suppose that you have two classes Real and PositiveReal such that PositiveReal extends Real, and furthermore assume that Real is Comparable.

Suppose that your function Weight from the previous example actually has a slightly more precise type Weight extends Function<PhysicalObject, PositiveReal>. If the declaration of keyExtractor were Function<? super T, U> instead of Function<? super T, ? extends U>, you wouldn't be able to make use of the fact that PositiveReal is also a Real, and therefore two PositiveReals couldn't be compared with each other, even though they implement Comparable<Real>, without the unnecessary restriction Comparable<PositiveReal>.

To summarize: with the declaration Function<? super T, ? extends U>, the Weight extends Function<PhysicalObject, PositiveReal> can be substituted for a Function<? super Car, ? extends Real> to compare Cars using the Comparable<Real>.

I hope this simple example clarifies why such a declaration is useful.


Code: Full enumeration of the consequences when either ? extends or ? super is omitted

Here is a compilable example with a systematic enumeration of all things that can possibly go wrong if we omit either ? super or ? extends. Also, two (ugly) partial work-arounds are shown.

import java.util.function.Function;
import java.util.Comparator;

class HypotheticComparators {

  public static <A, B> Comparator<A> badCompare1(Function<A, B> f, Comparator<B> cb) {
    return (A a1, A a2) -> cb.compare(f.apply(a1), f.apply(a2));
  }

  public static <A, B> Comparator<A> badCompare2(Function<? super A, B> f, Comparator<B> cb) {
    return (A a1, A a2) -> cb.compare(f.apply(a1), f.apply(a2));
  }

  public static <A, B> Comparator<A> badCompare3(Function<A, ? extends B> f, Comparator<B> cb) {
    return (A a1, A a2) -> cb.compare(f.apply(a1), f.apply(a2));
  }

  public static <A, B> Comparator<A> goodCompare(Function<? super A, ? extends B> f, Comparator<B> cb) {
    return (A a1, A a2) -> cb.compare(f.apply(a1), f.apply(a2));
  }

  public static void main(String[] args) {

    class PhysicalObject { double weight; }
    class Car extends PhysicalObject {}
    class Real { 
      private final double value; 
      Real(double r) {
        this.value = r;
      }
      double getValue() {
        return value;
      }
    }
    class PositiveReal extends Real {
      PositiveReal(double r) {
        super(r);
        assert(r > 0.0);
      }
    }

    Comparator<Real> realComparator = (Real r1, Real r2) -> {
      double v1 = r1.getValue();
      double v2 = r2.getValue();
      return v1 < v2 ? 1 : v1 > v2 ? -1 : 0;
    };
    Function<PhysicalObject, PositiveReal> weight = p -> new PositiveReal(p.weight);

    // bad "weight"-function that cannot guarantee that the outputs 
    // are positive
    Function<PhysicalObject, Real> surrealWeight = p -> new Real(p.weight);

    // bad weight function that works only on cars
    // Note: the implementation contains nothing car-specific,
    // it would be the same for every other physical object!
    // That means: code duplication!
    Function<Car, PositiveReal> carWeight = p -> new PositiveReal(p.weight); 

    // Example 1
    // badCompare1(weight, realComparator); // doesn't compile
    // 
    // type error:
    // required: Function<A,B>,Comparator<B>
    // found: Function<PhysicalObject,PositiveReal>,Comparator<Real>

    // Example 2.1
    // Comparator<Car> c2 = badCompare2(weight, realComparator); // doesn't compile
    // 
    // type error:    
    // required: Function<? super A,B>,Comparator<B>
    // found: Function<PhysicalObject,PositiveReal>,Comparator<Real>

    // Example 2.2
    // This compiles, but for this to work, we had to loosen the output
    // type of `weight` to a non-necessarily-positive real number
    Comparator<Car> c2_2 = badCompare2(surrealWeight, realComparator);

    // Example 3.1
    // This doesn't compile, because `Car` is not *exactly* a `PhysicalObject`:
    // Comparator<Car> c3_1 = badCompare3(weight, realComparator); 
    // 
    // incompatible types: inferred type does not conform to equality constraint(s)
    // inferred: Car
    // equality constraints(s): Car,PhysicalObject

    // Example 3.2
    // This works, but with a bad code-duplicated `carWeight` instead of `weight`
    Comparator<Car> c3_2 = badCompare3(carWeight, realComparator);

    // Example 4
    // That's how it's supposed to work: compare cars by their weights. Done!
    Comparator<Car> goodComparator = goodCompare(weight, realComparator);

  }
}

Related links

  1. Detailed illustration of definition-site covariance and contravariance in Scala: How to check covariant and contravariant position of an element in the function?
Cheliform answered 6/3, 2018 at 6:29 Comment(10)
Thanks Andrey ! I think that i understand the logic behind using the bounded wildcard now but not fully convinced on why should we have it in the comparing function. I update my question with an example to show my thought. Comparator.comparing take a function which is a key extractor and return a comparator, Without the bounded wildcard, this pattern seems to be working fine. I also have a hard time translating your example into a quick code sample, do you mind give me some guidance how to understand it better with some code sample ?Saltish
@JiamingLi OK, I'll add a full compilable code example in a minute.Cheliform
@JiamingLi Added full enumeration of all possible ways how it can go wrong without the wildcards.Cheliform
@JiamingLi glad to help! I also find it a nice exercise to quantify exactly how much the behavior of badCompareN deteriorates if we omit different variance annotations, and how much can be rescued by various work-arounds. I had fun answering the question, so thank you too. :]Cheliform
So, following your example, the whole thing would look like this: static <Car, Real extends Comparable<? super Real>> Comparator<Car> comparing(Function<? super Car,? extends Real> keyExtractor), where the super of Real is Real, the super of Car is PhysicalObject and PositiveReal extends Real (according with the weight function signature).Anderegg
@Anderegg I'm not sure what you attempted to express with your code, but I'm pretty sure that I didn't use "Car" or "Real" as type variable names anywhere. All type variable names in the above example are single upper case letters. Moreover, the Real extends Comparable<? super Real> looks very peculiar, I don't see anything similar in my example. Given the length of the code snippet in your comment, I'd consider asking a separate question, and possibly linking this one in the comment.Cheliform
@AndreyTyukin I was referring to the "Informal discussion of the problem" of your answer. You use a return type Tcalled Car that extends PhysicalObject, a method called Weight that extends Function<PhysicalObject, PositiveReal> and return type U called PositiveReal that extends Real (where Real is comparable). I was asking about the Comparator.comparing signature, according to the logic u show in this theoretical example.Anderegg
@Anderegg The signature of Comparator.comparing is given by standard API, it is shown in the original question. When applied to the specific Weight function in appropriate context, the type variables T and U from the signature of Comparator.comparing would be instantiated to T = Car, U = Real, and the two wildcards in Function<? super T, ? extends U> would match PhysicalObject and PositiveReal respectively (which fits nicely with bindings of T and U, because both PhysicalObject super Car and PositiveReal extends Real hold). Is that what you were asking?Cheliform
I think this is indeed what you were asking, because now that I look again at your code snippet, it seems to express roughly the same idea that I described in the previous paragraph.Cheliform
@AndreyTyukin Yes that's what I was basically asking. Like you said, I was trying to roughly show the method "signature" with T = Car and U = Real, just to see the general "panorama", due I was getting confused when looking at each part separately.Anderegg
E
6

Let's say, for example, we want to compare commercial flights by what plane they use. We would therefore need a method that takes in a flight, and returns a plane:

Plane func (CommercialFlight)

That is, of course, a Function<CommercialFlight, Plane>.

Now, the important thing is that the function returns a Plane. It doesn't matter what kind of plane is returned. So a method like this should also work:

CivilianPlane func (CommercialFlight)

Now technically this is a Function<CommercialFlight, CivilianPlane>, which is not the same as a Function<CommercialFlight, Plane>. So without theextends`, this function wouldn't be allowed.

Similarly, the other important thing is that is can accept a CommercialFlight as an argument. So a method like this should also work:

Plane func (Flight)

Technically, this is a Function<Flight, Plane>, which is also not the same as a Function<CommercialFlight, Plane>. So without the super, this function wouldn't be allowed either.

Escapement answered 6/3, 2018 at 6:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.