Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Comparator comparing static function

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));
}
like image 618
Jiaming Li Avatar asked Mar 06 '18 06:03

Jiaming Li


People also ask

Which is better Comparator or comparable in Java?

Comparable vs Comparator. Comparable interface can be used to provide single way of sorting whereas Comparator interface is used to provide different ways of sorting. For using Comparable, Class needs to implement it whereas for using Comparator we don't need to make any change in the class.

Can we use compareTo in Comparator?

Comparator detaches the sorting logic from your object and contains the compareTo() logic within your sort() method.

How does Compare method of Comparator work in Java?

The compare Method obj1 and obj2 are the objects to be compared. This method returns zero if the objects are equal. It returns a positive value if obj1 is greater than obj2. Otherwise, a negative value is returned.

What features Java 8 has given for Comparator?

Comparator has undergone a major overhaul in Java 8 while still retaining its essence which is to compare and sort objects in Collections. Comparator now supports declarations via lambda expressionsRead Lambda Expressions Tutorial as it is a Functional Interface.


2 Answers

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?
like image 110
Andrey Tyukin Avatar answered Oct 22 '22 13:10

Andrey Tyukin


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.

like image 42
Joe C Avatar answered Oct 22 '22 13:10

Joe C