Компаратор Java 8, сравнивающий статическую функцию

Для сравнения исходного кода в классе Comparator

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));
}

Я понимаю разницу между super и extends. Чего я не понимаю, так это почему этот метод имеет их. Может ли кто-нибудь привести пример того, чего нельзя достичь, когда параметр выглядит так Function<T, U> keyExtractor?

Например :

Comparator<Employee> employeeNameComparator = Comparator.comparing(Employee::getName);

также может скомпилироваться со следующим определением функции

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));
}

person Jiaming Li    schedule 06.03.2018    source источник
comment
Как написано, это не по теме, просьба привести пример/рекомендацию. Может быть, вы могли бы перефразировать вопрос с дополнительным контекстом того, чего вы пытаетесь достичь и что именно сбивает с толку, может быть, примеры того, что вы пробовали, но это не работает?   -  person Jim Garrison    schedule 06.03.2018
comment
Можете ли вы дать дополнительные пояснения о приведении (Comparator‹T› и Serializable), используемом при возврате? Что они имеют в виду ?   -  person e2a    schedule 27.02.2019


Ответы (2)


Вот простой пример: сравнение автомобилей по весу. Сначала я опишу проблему в текстовой форме, а затем продемонстрирую всеми возможными способами, как она может пойти не так, если опустить ? 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);

  }
}

Ссылки по теме

  1. Подробная иллюстрация ковариантности и контравариантности сайта определения в Scala: Как проверить ковариантное и контравариантное положение элемента в функции?
person Andrey Tyukin    schedule 06.03.2018
comment
Спасибо Андрей! Я думаю, что теперь я понимаю логику использования ограниченного подстановочного знака, но не полностью убежден в том, почему мы должны использовать его в функции сравнения. Я обновляю свой вопрос примером, чтобы показать свою мысль. Comparator.comparing берет функцию, которая является экстрактором ключей, и возвращает компаратор. Без ограниченного подстановочного знака этот шаблон работает нормально. Мне также трудно перевести ваш пример в пример быстрого кода, не могли бы вы дать мне несколько советов, как лучше понять его с помощью некоторого примера кода? - person Jiaming Li; 06.03.2018
comment
@JiamingLi Хорошо, через минуту я добавлю полный пример компилируемого кода. - person Andrey Tyukin; 06.03.2018
comment
@JiamingLi Добавлено полное перечисление всех возможных способов, как это может пойти не так, без подстановочных знаков. - person Andrey Tyukin; 06.03.2018
comment
@JiamingLi рад помочь! Я также нахожу хорошим упражнением количественную оценку того, насколько поведение badCompareN ухудшится, если мы опустим различные аннотации дисперсии, и сколько можно исправить с помощью различных обходных путей. Мне было весело отвечать на вопрос, так что спасибо и вам. :] - person Andrey Tyukin; 06.03.2018
comment
Итак, следуя вашему примеру, все это будет выглядеть так: static <Car, Real extends Comparable<? super Real>> Comparator<Car> comparing(Function<? super Car,? extends Real> keyExtractor), где super of Real - это Real, super of Car - PhysicalObject, а PositiveReal расширяет Real (в соответствии с сигнатурой функции веса). - person DamianGDO; 14.04.2021
comment
@DamianGDO Я не уверен, что вы пытались выразить своим кодом, но я почти уверен, что нигде не использовал Car или Real в качестве имен переменных типа. Все имена переменных типа в приведенном выше примере представляют собой одну заглавную букву. Более того, Real extends Comparable<? super Real> выглядит очень своеобразно, ничего похожего я не вижу в своем примере. Учитывая длину фрагмента кода в вашем комментарии, я бы подумал о том, чтобы задать отдельный вопрос и, возможно, связать его в комментарии. - person Andrey Tyukin; 14.04.2021
comment
@AndreyTyukin Я имел в виду Неофициальное обсуждение проблемы вашего ответа. Вы используете тип возвращаемого значения Tназываемый Car, который extends PhysicalObject, метод, называемый Weight, который extends Function<PhysicalObject, PositiveReal>, и тип возвращаемого значения U, называемый PositiveReal, который extends Real (где Real сопоставим). Я спрашивал о подписи Comparator.comparing, согласно логике, которую вы показываете в этом теоретическом примере. - person DamianGDO; 14.04.2021
comment
@DamianGDO Подпись Comparator.comparing предоставляется стандартным API, она показана в исходном вопросе. При применении к конкретной функции Weight в соответствующем контексте переменные типа T и U из сигнатуры Comparator.comparing будут преобразованы в T = Car, U = Real, а два подстановочных знака в Function<? super T, ? extends U> будут соответствовать PhysicalObject и PositiveReal соответственно (что хорошо сочетается с привязками T). и U, потому что и PhysicalObject super Car, и PositiveReal extends Real сохраняются). Это то, что вы спрашивали? - person Andrey Tyukin; 14.04.2021
comment
Я думаю, это действительно то, о чем вы спрашивали, потому что теперь, когда я снова смотрю на ваш фрагмент кода, он, кажется, выражает примерно ту же идею, которую я описал в предыдущем абзаце. - person Andrey Tyukin; 14.04.2021
comment
@AndreyTyukin Да, это то, о чем я в основном спрашивал. Как вы сказали, я пытался примерно показать сигнатуру метода с помощью T = Car и U = Real, просто чтобы увидеть общую панораму, потому что я запутался, глядя на каждую часть в отдельности. - person DamianGDO; 14.04.2021

Скажем, например, мы хотим сравнить коммерческие рейсы, на каком самолете они летают. Поэтому нам нужен метод, который принимает полет и возвращает самолет:

Plane func (CommercialFlight)

Это, конечно, Function<CommercialFlight, Plane>.

Теперь важно то, что функция возвращает Plane. Неважно, какой самолет возвращается. Таким образом, такой метод также должен работать:

CivilianPlane func (CommercialFlight)

Теперь технически это Function<CommercialFlight, CivilianPlane>, что не то же самое, что Function<CommercialFlight, Plane>. So without theextends`, эта функция не разрешена.

Точно так же другая важная вещь заключается в том, что он может принимать CommercialFlight в качестве аргумента. Таким образом, такой метод также должен работать:

Plane func (Flight)

Технически это Function<Flight, Plane>, но это не то же самое, что Function<CommercialFlight, Plane>. Так что без super эта функция тоже была бы недоступна.

person Joe C    schedule 06.03.2018