javagenericsbounded-wildcard

Java 8 Comparator comparing static function


For the comparing source code in java.util.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));
}

Solution

  • 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?