Java Lambda Java-8 Java-Stream – Java8 Streams Sequential and Parallel Execution Produce Different Results?

javajava-8java-streamlambda

Running the following stream example in Java8:

    System.out.println(Stream
        .of("a", "b", "c", "d", "e", "f")
        .reduce("", (s1, s2) -> s1 + "/" + s2)
    );

yields:

/a/b/c/d/e/f

Which is – of course – no surprise.
Due to http://docs.oracle.com/javase/8/docs/api/index.html?overview-summary.html it shouldn't matter whether the stream is executed sequentially or parallel:

Except for operations identified as explicitly nondeterministic, such as findAny(), whether a stream executes sequentially or in parallel should not change the result of the computation.

AFAIK reduce() is deterministic and (s1, s2) -> s1 + "/" + s2 is associative, so that adding parallel() should yield the same result:

    System.out.println(Stream
            .of("a", "b", "c", "d", "e", "f")
            .parallel()
            .reduce("", (s1, s2) -> s1 + "/" + s2)
    );

However the result on my machine is:

/a//b//c//d//e//f

What's wrong here?

BTW: using (the preferred) .collect(Collectors.joining("/")) instead of reduce(...) yields the same result a/b/c/d/e/f for sequential and parallel execution.

JVM details:

java.specification.version: 1.8
java.version: 1.8.0_31
java.vm.version: 25.31-b07
java.runtime.version: 1.8.0_31-b13

Best Answer

From reduce's documentation:

The identity value must be an identity for the accumulator function. This means that for all t, accumulator.apply(identity, t) is equal to t.

Which is not true in your case - "" and "a" creates "/a".

I have extracted the accumulator function and added a printout to show what happens:

BinaryOperator<String> accumulator = (s1, s2) -> {
    System.out.println("joining \"" + s1 + "\" and \"" + s2 + "\"");
    return s1 + "/" + s2;
};
System.out.println(Stream
                .of("a", "b", "c", "d", "e", "f")
                .parallel()
                .reduce("", accumulator)
);

This is example output (it differs between runs):

joining "" and "d"
joining "" and "f"
joining "" and "b"
joining "" and "a"
joining "" and "c"
joining "" and "e"
joining "/b" and "/c"
joining "/e" and "/f"
joining "/a" and "/b//c"
joining "/d" and "/e//f"
joining "/a//b//c" and "/d//e//f"
/a//b//c//d//e//f

You can add an if statement to your function to handle empty string separately:

System.out.println(Stream
        .of("a", "b", "c", "d", "e", "f")
        .parallel()
        .reduce((s1, s2) -> s1.isEmpty()? s2 : s1 + "/" + s2)
);

As Marko Topolnik noticed, checking s2 is not required as accumulator doesn't have to be commutative function.

Related Question