【问题标题】:Java 8 Comparator comparing static functionJava 8 Comparator 比较静态函数
【发布时间】:2018-08-13 22:20:02
【问题描述】:

用于比较器类中的比较源代码

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

我了解superextends 之间的区别。我不明白的是为什么这种方法有它们。当参数看起来像这样Function&lt;T, U&gt; 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));
}

【问题讨论】:

  • 正如所写,这是离题的边缘,要求提供示例/建议。也许您可以用更多关于您要实现的目标以及令人困惑的内容的更多上下文来重新表述这个问题,也许您尝试过的示例不起作用?
  • 你能提供更多关于返回时使用的转换(Comparator & Serializable)的解释吗?它们是什么意思?

标签: java generics bounded-wildcard


【解决方案1】:

这是一个简单的例子:按重量比较汽车。我将首先以文本形式描述问题,然后演示如果省略? extends? super,它会如何出错。我还展示了在每种情况下都可用的丑陋的部分解决方法。 如果你更喜欢代码而不是散文,直接跳到第二部分,它应该是不言自明的。


问题的非正式讨论

首先,逆变器? super T

假设您有两个类CarPhysicalObject,这样Car extends PhysicalObject。现在假设您有一个扩展 Function&lt;PhysicalObject, Double&gt; 的函数 Weight

如果声明是Function&lt;T,U&gt;,那么您不能重用函数Weight extends Function&lt;PhysicalObject, Double&gt; 来比较两辆汽车,因为Function&lt;PhysicalObject, Double&gt; 不符合Function&lt;Car, Double&gt;。但您显然希望能够按重量比较汽车。因此,逆变式? super T 是有意义的,因此Function&lt;PhysicalObject, Double&gt; 符合Function&lt;? super Car, Double&gt;


现在是协变 ? extends U 声明。

假设您有两个类RealPositiveReal,这样PositiveReal extends Real,并假设RealComparable

假设您在前面示例中的函数Weight 实际上有一个更精确的类型Weight extends Function&lt;PhysicalObject, PositiveReal&gt;。如果keyExtractor 的声明是Function&lt;? super T, U&gt; 而不是Function&lt;? super T, ? extends U&gt;,您将无法利用PositiveReal 也是Real 的事实,因此两个PositiveReals 不能相互比较,即使它们实现了Comparable&lt;Real&gt;,也没有不必要的限制Comparable&lt;PositiveReal&gt;

总结一下:通过声明Function&lt;? super T, ? extends U&gt;Weight extends Function&lt;PhysicalObject, PositiveReal&gt; 可以替换为Function&lt;? super Car, ? extends Real&gt; 以使用Cars 比较Comparable&lt;Real&gt;

我希望这个简单的例子能阐明为什么这样的声明是有用的。


代码:当? 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 中定义点协变和逆变的详细说明:How to check covariant and contravariant position of an element in the function?

【讨论】:

  • 谢谢安德烈!我认为我现在了解使用有界通配符背后的逻辑,但不完全相信我们为什么要在比较函数中使用它。我用一个例子来更新我的问题来展示我的想法。 Comparator.comparing 采用一个作为键提取器的函数并返回一个比较器,如果没有有界通配符,这种模式似乎工作正常。我也很难将您的示例转换为快速代码示例,您介意给我一些指导如何通过一些代码示例更好地理解它吗?
  • @JiamingLi 好的,我稍后会添加一个完整的可编译代码示例。
  • @JiamingLi 添加了所有可能的方式的完整枚举,如果没有通配符,它​​可能会出错。
  • 所以,按照你的例子,整个事情看起来像这样:static &lt;Car, Real extends Comparable&lt;? super Real&gt;&gt; Comparator&lt;Car&gt; comparing(Function&lt;? super Car,? extends Real&gt; keyExtractor),其中 Real 的超级是 Real,Car 的超级是 PhysicalObject 并且 PositiveReal 扩展 Real(根据权重函数签名)。
  • @DamianGDO Comparator.comparing 的签名由标准 API 给出,如原始问题所示。当在适当的上下文中应用于特定的Weight 函数时,Comparator.comparing 签名中的类型变量TU 将被实例化为T = CarU = RealFunction&lt;? super T, ? extends U&gt; 中的两个通配符将分别匹配 PhysicalObjectPositiveReal (这非常适合 TU 的绑定,因为 PhysicalObject super CarPositiveReal extends Real 都持有)。你问的是这个吗?
【解决方案2】:

假设,例如,我们想比较商业航班所使用的飞机。因此,我们需要一个接收航班并返回飞机的方法:

Plane func (CommercialFlight)

当然是Function&lt;CommercialFlight, Plane&gt;

现在,重要的是该函数返回一个Plane。归还什么样的飞机并不重要。所以这样的方法也应该有效:

CivilianPlane func (CommercialFlight)

从技术上讲,这是一个Function&lt;CommercialFlight, CivilianPlane&gt;,它与Function&lt;CommercialFlight, Plane&gt;. So without theextends 不同,这个函数是不允许的。

同样,另一个重要的事情是可以接受CommercialFlight 作为参数。所以这样的方法也应该有效:

Plane func (Flight)

从技术上讲,这是一个Function&lt;Flight, Plane&gt;,它也不同于Function&lt;CommercialFlight, Plane&gt;。所以没有super,这个函数也不会被允许。

【讨论】:

    猜你喜欢
    • 2017-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多