Java 8方法签名不一致

时间:2015-03-24 20:31:02

标签: java java-8

Java 8为我们提供了具有如此长签名的新方法:

static <T,K,U,M extends Map<K,U>> Collector<T,?,M> toMap(
    Function<? super T,? extends K> keyMapper, 
    Function<? super T,? extends U> valueMapper, 
    BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)

我觉得奇怪的是,已经使用通配符来确保前两个参数尽可能通用,但第三个参数只是BinaryOperator<U>。如果他们一致,肯定会是BiFunction<? super U,? super U,? extends U>?我错过了什么吗?有没有充分的理由,或者他们只是想避免使已经可怕的签名更糟糕?

修改

我了解PECS,并且我理解mergeFunction应被视为采用两个U并获得U的方式的原则。然而,能够拥有可以以许多不同方式重用的对象将是有用的。例如:

static final BiFunction<Number, Number, Double> 
        MULTIPLY_DOUBLES = (a, b) -> a.doubleValue() * b.doubleValue();

显然这不是BinaryOperator<Double>,但可以将视为。如果您可以将MULTIPLY_DOUBLES用作两个 BiFunction<Number, Number, Double>BinaryOperator<Double>,这将会很棒,具体取决于具体情况。特别是,您可以简单地传递MULTIPLY_DOUBLES以表示您希望使用乘法减少double s的加载。但是toMap(以及Java 8中的其他新方法)的签名不允许这种灵活性。

3 个答案:

答案 0 :(得分:3)

BinaryOperator<U> mergeFunction需要从输入源获取U并将它们放入另一个消费者。

由于Get和Put原则,类型必须完全相同。没有外卡。

  

get-put原则,如Naftalin and Wadler's fine book on generics, Java Generics and Collections所述:

     

当你只从结构中获取值时使用扩展通配符,当你只将值放入结构时使用超级通配符,而当你同时使用通配符时使用通配符。

因此我们不能BiFunction<? super U,? super U,? extends U> mergefunction,因为我们正在执行getput操作。因此,输入和结果类型必须相同。

有关Get and Put的更多信息,请参阅以下其他链接:

Explanation of the get-put principle(问题)

http://www.ibm.com/developerworks/library/j-jtp07018/

修改

正如Gab指出的那样,“生产者扩展消费者超级”的缩写PECS也知道Get和Put原则

What is PECS (Producer Extends Consumer Super)?

答案 1 :(得分:2)

查看有问题的Collectors#toMap的实现,可以看到运算符被传递给其他一些方法,但最终只以remappingFunction的形式到达Map#merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)的各种形式}。

因此,使用BiFunction<? super V, ? super V, ? extends V>代替BinaryOperator<V>确实可以在这里工作,而不会造成任何问题。但不仅仅是这里:BinaryOperator只是BiFunction的特化,因为操作数和结果都是相同的类型。所以有很多的地方允许传入BiFunction<? super V, ? super V, ? extends V>代替BinaryOperator<V>(或者更明显:一个人可以总是使用BiFunction<V, V, V>代替......)


到目前为止,似乎没有技术理由为什么他们选择仅支持BinaryOperator<U>

关于可能的非技术原因已经有人猜测。例如,限制方法签名的复杂性。我不确定这是否适用于此,但它确实可以在方法的复杂性和预期的应用案例之间进行权衡:“二元运算符”的概念很容易理解,例如,通过绘制在这种情况下,类似于简单的添加或两个集合的联合 - 或映射

可能不那么明显的技术原因可能是应该有可能提供此方法的实现,内部 无法处理BiFunction。但考虑到BinaryOperator只是一种专业化,很难想象这样的实现应该是什么样子。

答案 2 :(得分:2)

你是对的, merge 操作的功能签名(同样适用于 reduce )不需要像BinaryOperator这样的接口。

这不仅可以通过以下事实来说明:[{1}}收藏家的mergeFunction最终会在Map.merge接受toMap;您还可以将此类BiFunction<? super V,? super V,? extends V>转换为所需的BiFunction

BinaryOperator

或完全通用:

BiFunction<Number, Number, Double> 
    MULTIPLY_DOUBLES = (a, b) -> a.doubleValue() * b.doubleValue();
Stream<Double> s = Stream.of(42.0, 0.815);
Optional<Double> n=s.reduce(MULTIPLY_DOUBLES::apply);

创建public static <T> Optional<T> reduce( Stream<T> s, BiFunction<? super T, ? super T, ? extends T> f) { return s.reduce(f::apply); } BinaryOperator的最可能原因是与这些函数的原始类型版本具有对称性,这些函数没有这样的超级接口。

在这方面,方法 一致

  • UnaryOperator
  • Stream.reduce(BinaryOperator<T>)
  • IntStream.reduce(IntBinaryOperator)
  • DoubleStream.reduce(DoubleBinaryOperator)

  • LongStream.reduce(LongBinaryOperator)
  • Arrays.parallelPrefix(T[] array, BinaryOperator<T> op)
  • Arrays.parallelPrefix(int[] array, IntBinaryOperator op)
  • Arrays.parallelPrefix(double[] array, DoubleBinaryOperator op)