Вот простой пример: сравнение автомобилей по весу. Сначала я опишу проблему в текстовой форме, а затем продемонстрирую всеми возможными способами, как она может пойти не так, если опустить ? extends
или ? super
. Я также показываю уродливые частичные обходные пути, которые доступны в каждом случае. Если вы предпочитаете код тексту, сразу переходите ко второй части, она не требует пояснений.
Неформальное обсуждение проблемы
Во-первых, контравариант ? super T
.
Предположим, что у вас есть два класса Car
и PhysicalObject
, такие что Car extends PhysicalObject
. Теперь предположим, что у вас есть функция Weight
, расширяющая Function<PhysicalObject, Double>
.
Если бы объявление было Function<T,U>
, то вы не могли бы повторно использовать функцию Weight extends Function<PhysicalObject, Double>
для сравнения двух автомобилей, потому что Function<PhysicalObject, Double>
не соответствовало бы Function<Car, Double>
. Но вы, очевидно, хотите сравнивать автомобили по их весу. Следовательно, контравариант ? super T
имеет смысл, так что Function<PhysicalObject, Double>
соответствует Function<? super Car, Double>
.
Теперь ковариантное объявление ? extends U
.
Предположим, что у вас есть два класса Real
и PositiveReal
, такие что PositiveReal extends Real
, и, кроме того, предположим, что Real
равно Comparable
.
Предположим, что ваша функция Weight
из предыдущего примера на самом деле имеет несколько более точный тип Weight extends Function<PhysicalObject, PositiveReal>
. Если бы объявление keyExtractor
было Function<? super T, U>
вместо Function<? super T, ? extends U>
, вы не смогли бы использовать тот факт, что PositiveReal
также является Real
, и поэтому два PositiveReal
нельзя сравнивать друг с другом, даже если они реализуют Comparable<Real>
без ненужное ограничение Comparable<PositiveReal>
.
Подводя итог: с объявлением Function<? super T, ? extends U>
Weight extends Function<PhysicalObject, PositiveReal>
можно заменить на Function<? super Car, ? extends Real>
для сравнения Car
с использованием Comparable<Real>
.
Я надеюсь, что этот простой пример поясняет, почему такое объявление полезно.
Код: полное перечисление последствий, когда ? extends
или ? super
опущены.
Вот компилируемый пример с систематическим перечислением всего, что может пойти не так, если мы опустим ? super
или ? extends
. Кроме того, показаны два (уродливых) частичных обходных пути.
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);
}
}
Ссылки по теме
- Подробная иллюстрация ковариантности и контравариантности сайта определения в Scala: Как проверить ковариантное и контравариантное положение элемента в функции?
person
Andrey Tyukin
schedule
06.03.2018