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中的其他新方法)的签名不允许这种灵活性。
答案 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
,因为我们正在执行get
和put
操作。因此,输入和结果类型必须相同。
有关Get and Put的更多信息,请参阅以下其他链接:
Explanation of the get-put principle(问题)
http://www.ibm.com/developerworks/library/j-jtp07018/
修改强>
正如Gab指出的那样,“生产者扩展消费者超级”的缩写PECS也知道Get和Put原则
答案 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)