Java 8 comparator comparing static function

To compare the source code in the 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 . I do not understand why this method has them. Can someone give me an example of what cannot be achieved when the parameter looks like this: Function<T, U> keyExtractor ?

For instance:

 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)); } 
+6
source share
2 answers

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


Informal discussion of the problem

First of all, contraception ? super T ? super T ? super T ? super T

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

If the declaration was Function<T,U> , then you could not reuse the function. Weight extends Function<PhysicalObject, Double> to compare two cars, because Function<PhysicalObject, Double> does not match Function<Car, Double> . But you obviously want to compare cars by weight. Consequently, contra-option ? super T ? super T ? super T ? super T makes sense, so that Function<PhysicalObject, Double> corresponds to Function<? super Car, Double> Function<? super Car, Double> Function<? super Car, Double> Function<? super Car, Double> .


Now covariant ? extends U ? extends U ? extends U ? extends U declaration ? extends U ? extends U

Suppose you have two classes Real and PositiveReal such that PositiveReal extends Real and, moreover, suppose that Real is Comparable .

Suppose your Weight function from the previous example actually has a slightly more accurate type. Weight extends Function<PhysicalObject, PositiveReal> . If the keyExtractor was Function<? super T, U> Function<? super T, U> Function<? super T, U> Function<? super T, U> instead of Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> , you cannot use the fact that PositiveReal also Real , and therefore two PositiveReal cannot be compared with each other, even if they implement Comparable<Real> without unnecessary restriction. Comparable<PositiveReal> .

To summarize: with the declaration Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> Function<? super T,? extends U> , Weight extends Function<PhysicalObject, PositiveReal> can be replaced by Function<? super Car,? extends Real> Function<? super Car,? extends Real> Function<? super Car,? extends Real> Function<? super Car,? extends Real> Function<? super Car,? extends Real> Function<? super Car,? extends Real> Function<? super Car,? extends Real> Function<? super Car,? extends Real> Function<? super Car,? extends Real> compare Car with Comparable<Real> .

Hope this simple example clarifies why such a declaration is useful.


Code: A full list of consequences when ? extends ? extends ? extends ? extends or ? super ? super ? super ? super omitted

Here's a compiled example with a systematic listing of all the things that might go wrong if we omit it too ? super ? super ? super ? super or ? extends ? extends ? extends ? extends Two (ugly) partial workarounds are also 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 how it supposed to work: compare cars by their weights. Done! Comparator<Car> goodComparator = goodCompare(weight, realComparator); } } 

Related Links

  1. A detailed illustration of the covariance and contravariance of a definition in Scala: how to check the covariant and contravariant position of an element in a function?
+8
source

Say, for example, we want to compare commercial flights on which plane they use. Therefore, we need a method that takes a flight and returns a plane:

 Plane func (CommercialFlight) 

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

Now the important thing is that the function returns Plane . It doesn't matter which plane returns. Therefore, this method should also work:

 CivilianPlane func (CommercialFlight) 

Now technically it is Function<CommercialFlight, CivilianPlane> , which does not match Function<CommercialFlight, Plane>. So without the Function<CommercialFlight, Plane>. So without the extends`, this function will not be allowed.

Likewise, another important thing is that it can accept CommercialFlight as an argument. Therefore, this method should also work:

 Plane func (Flight) 

Technically, this is Function<Flight, Plane> , which also does not match Function<CommercialFlight, Plane> . Thus, without super this function will also not be allowed.

+3
source

Source: https://habr.com/ru/post/1275728/


All Articles