Inconsistency in Java 8 method signatures

后端 未结 3 432
名媛妹妹
名媛妹妹 2021-02-08 09:46

Java 8 has given us new methods with really long signatures like this:

static > Collector toMap(
    Function&l         


        
相关标签:
3条回答
  • 2021-02-08 10:21

    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.

    0 讨论(0)
  • 2021-02-08 10:25

    the BinaryOperator<U> mergeFunction needs to take Us 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)?

    0 讨论(0)
  • 2021-02-08 10:27

    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)
    0 讨论(0)
提交回复
热议问题