Java 8 Comparator comparing static function

2020-03-01 18:03发布

问题:

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));
}

回答1:

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?


回答2:

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.