问题
Java 8 has given us new methods with really long signatures like this:
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)
What I find odd about this is that wildcards have been used to ensure that the first two parameters are as general as possible, yet the third parameter is just a BinaryOperator<U>
. If they'd been consistent, surely it would be a BiFunction<? super U,? super U,? extends U>
?. Am I missing something? Is there a good reason for this, or did they just want to avoid making an already horrendous signature even worse?
Edit
I understand PECS, and I understand the principle that mergeFunction
should be thought of as a way of taking two U
s and getting back a U
. However it would be useful to be able to have an object that could be reused in many different ways. For example:
static final BiFunction<Number, Number, Double>
MULTIPLY_DOUBLES = (a, b) -> a.doubleValue() * b.doubleValue();
Obviously this is not a BinaryOperator<Double>
, but it could be treated as one. It would be great if you could use MULTIPLY_DOUBLES
as both a BiFunction<Number, Number, Double>
and a BinaryOperator<Double>
, depending on the context. In particular, you could simply pass MULTIPLY_DOUBLES
to indicate that you want a load of double
s to be reduced using multiplication. However the signature for toMap
(and other new methods in Java 8) does not allow for this kind of flexibility.
回答1:
You are right in that the functional signature of the merge operation (the same applies to reduce) does not require an interface like BinaryOperator
.
This can not only be illustrated by the fact that the mergeFunction
of the toMap
collector will end up at Map.merge which accepts a BiFunction<? super V,? super V,? extends V>
; you can also convert such a BiFunction
to the required 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);
or full generic:
public static <T> Optional<T> reduce(
Stream<T> s, BiFunction<? super T, ? super T, ? extends T> f) {
return s.reduce(f::apply);
}
The most likely reason for creating BinaryOperator
and UnaryOperator
is to have symmetry with the primitive type versions of these functions which don’t have such a super interface.
In this regard, the methods are consistent
Stream.reduce(BinaryOperator<T>)
IntStream.reduce(IntBinaryOperator)
DoubleStream.reduce(DoubleBinaryOperator)
LongStream.reduce(LongBinaryOperator)
or
Arrays.parallelPrefix(T[] array, BinaryOperator<T> op)
Arrays.parallelPrefix(int[] array, IntBinaryOperator op)
Arrays.parallelPrefix(double[] array, DoubleBinaryOperator op)
Arrays.parallelPrefix(long[] array, LongBinaryOperator op)
回答2:
the BinaryOperator<U> mergeFunction
needs to take U
s from an input source and put them into another consumer.
Due to the Get and Put Principle, the type has to be exactly the same. No wild cards.
The get-put principle, as stated in Naftalin and Wadler's fine book on generics, Java Generics and Collections says:
Use an extends wildcard when you only get values out of a structure, use a super wildcard when you only put values into a structure, and don't use a wildcard when you do both.
Therefore it can't beBiFunction<? super U,? super U,? extends U> mergefunction
because we are doing get
and put
operations. Therefore the input and result type must be identical.
see these other links for more about Get and Put:
Explanation of the get-put principle (SO question)
http://www.ibm.com/developerworks/library/j-jtp07018/
EDIT
As Gab points out, the Get and Put principle is also known by the Acronym PECS for "Producer Extends Consumer Super"
What is PECS (Producer Extends Consumer Super)?
回答3:
Looking at the implementation of the Collectors#toMap in question, one can see that the operator is passed through to some other methods, but eventually only arrives as the remappingFunction
in various forms of Map#merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction).
So using BiFunction<? super V, ? super V, ? extends V>
instead of BinaryOperator<V>
would indeed work here, without causing any problem. But not only here: The BinaryOperator is only a specialization of BiFunction for the case that the operands and the result are all of the same type. So there are many places where one could allow passing in a BiFunction<? super V, ? super V, ? extends V>
instead of a BinaryOperator<V>
(or, more obviously: One could always use a BiFunction<V, V, V>
instead...)
So up to this point, there seems to be no technical reason why they chose to only support a BinaryOperator<U>
.
There was already speculation about possible non-technical reasons. For example, limiting the complexity of the method signature. I'm not sure whether this applies here, but it could, indeed, be a trade-off between the complexity of the method and the intended application cases: The concept of a "binary operator" is easily comprehensible, for example, by drawing analogies to a simple addition or the union of two sets - or maps, in this case.
A possible not-so-obvious technical reason could be that there should be the possibility to provide implementations of this method that internally would not be able to cope with the BiFunction
. But considering that the BinaryOperator
is only a specialization, it's hard to imagine what such an implementation should look like.
来源:https://stackoverflow.com/questions/29242311/inconsistency-in-java-8-method-signatures