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);
Related Links
- 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?