3

A reduce method of a Java Stream:

<U> U reduce(U identity,
             BiFunction<U, ? super T, U> accumulator,
             BinaryOperator<U> combiner);

helps reduce the stream based on some property of objects in the stream.

However, it is not clear to me why the accumulator could not be more straightforward and serving the same purpose:

BiFunction<U, T, U> accumulator.

What additional flexibility does the wildcard in accumulator's signature provide (at the cost of reduced readability)?

Kedar Mhaswade
  • 4,535
  • 2
  • 25
  • 34
  • 5
    basically http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs - A `Stream` can that way be consumed by some `BiFunction<.., Number, ..>` for example. – zapl Mar 18 '16 at 12:01

2 Answers2

6

why the accumulator could not be more straightforward and serving the same purpose:

It could be but that would be more restrictive than needed. If you have say a

BiFunction<String, Object, String> accumulator = (a,b) -> a + b;

You should be able to pass it to a

String join = strings.stream().reduce("text: ", accumulator, (a, b) -> a + b);

or

String join = ints.stream().reduce("long num: ", accumulator, (a, b) -> a + b);

i.e. you can write one accumulator for both reductions.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Thanks! The example is a bit contrived though: `System.out.println(Stream.of(1, -2, 3).reduce("long num: ", accumulator, (a, b) -> a + b));` produces `long num: 1-23` -- not sure how useful that is. Also it seems to work because of the side-effects of how Java treats `+` with `String` operands. Would you actually use such functions in practice? – Kedar Mhaswade Mar 18 '16 at 12:36
  • 2
    @KedarMhaswade it is hard to say, but from the API designers point of view, it is almost certain that some one, some day would ask, why can't I do this if it was limit to `` – Peter Lawrey Mar 18 '16 at 12:43
  • 1
    Imo this is more due to guidelines: if narrower (or wider) type can be used in signature, then it should be used. – Alex Salauyou Mar 18 '16 at 13:21
  • These examples violate the API contract as non-empty strings are *not* the identity value for string concatenation. That’s why they produce nonsense as soon as you try it with a parallel stream. – Holger Mar 18 '16 at 15:40
  • @Kedar Mhaswade: the examples do not depend on the `+` operator for strings. It would be the same when you write explicitly: `accumulator = (a,b) -> a.concat(String.valueOf(b));`. You won’t find a more useful ad-hoc example as you need an *existing* `BiFunction` to demonstrate that you can reuse it, but since there are no such existing function instances in the JRE, you always have to define one within the example, thus it will always look contrived… – Holger Mar 18 '16 at 15:44
  • 1
    @Holger I believe that _as written_, the second example works because of [string concatenation](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.18.1). The other point is, since there are no compelling cases for such use, if this API had started with `BiFunction` could a future release make it `BiFunction` without breaking compatibility (and perhaps enhancing its use) _when the need arose_? – Kedar Mhaswade Mar 18 '16 at 16:03
3

It allows to use function or lambda where second argument type doesn't need to be exact type T of stream, but may be any of its supertype. This allows to:

  • create accumulators able to be used in streams of different types

  • use as accumulators existing methods accepting supertype of T.

For example, thus you can use for Stream<Integer> accumulator that accepts Number, or for Stream<String> accumulator accepting CharSequence or even Object.

More important, this follows common JDK API style: in every signature, where narrower or wider type can be used, it should be used. I never see this rule documented, but I don't know any exception to this rule in JDK.

See What is PECS SO question for a better explanation.


For example, accumulator to count distinct items of any stream, may look like:

 public class DistinctCounter {
     Set<Object> set = new HashSet<>();

     // count() accepts object of any type
     public int count(int prev, Object item) {
         return prev + set.add(item) ? 1 : 0;
     }
 }

and may be used for Stream<String> as follows:

Stream<String> stream = ...
final DistinctCounter c = new DistinctCounter();
int count = stream.reduce(0, c::count, Integer::add);

Though this example is not very practical, hope it will help to understand.

Community
  • 1
  • 1
Alex Salauyou
  • 14,185
  • 5
  • 45
  • 67