In collect() terminal operation of Stream, what will happen if supplier is an immutable object like a String?

Viewed 121

collect() method of Stream is a mutable reduction. Based on Java Documenation:

A mutable reduction operation accumulates input elements into a mutable result container, such as a Collection or StringBuilder, as it processes the elements in the stream.

I tried the following and it compiles without issue.

Stream<String> stream1 = Stream.of("w", "o", "l", "f");
String word = stream1.collect(String::new, String::concat, String::concat);
System.out.println(word);

If the supplier is a StringBuffer, I view the collect operation as elements will be appended to the supplied StringBuffer.

How does mutable reduction works here since String is an immutable object? Will it be the same as reduce operation where a new object is created everytime the accumulator is implemented?

2 Answers

How does mutable reduction works here since String is an immutable object?

It doesn't. When you run that you will get an empty String (the result of the Supplier only). The compiler can not enforce checking if the Supplier returns an immutable object, that is absolutely something it can not do. And since your container is immutable, updates to it are simply ignored. It's like doing:

String s = "abc";
s.concat("def"); // the result is ignored here

May be if you write that as a lambda it will make a lot more sense:

Stream<String> stream1 = Stream.of("w", "o", "l", "f");
    String word = stream1.collect(
            String::new,
            (left, right) -> {
                left.concat(right); // result is ignored
            },
            String::concat);

On the other hand, when you use reduce, you are forced to return something:

String word = stream1.reduce(
            "",
            (x, y) -> {
                return x.concat(y);
            },
            (x, y) -> {
                return x.concat(y);
            });

Of course, you could still do:

String word = stream1.reduce(
            "",
            (x, y) -> {
                x.concat(y);
                return x; // kind of stupid, but you could
            },
            (x, y) -> {
                return x.concat(y);
            });

If you wanted to break it; but that is not the point.

According to Oracle / Java documentation:

collect

<R> R collect(Supplier<R> supplier, <R,? super T> accumulator, <R,R> combiner)

Performs a mutable reduction operation on the elements of this stream. A mutable reduction is one in which the reduced value is a mutable result container, such as an ArrayList, and elements are incorporated by updating the state of the result rather than by replacing the result.

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#collect-java.util.function.Supplier-java.util.function.BiConsumer-java.util.function.BiConsumer-

Mutable reduction

A mutable reduction operation accumulates input elements into a mutable result container, such as a Collection or StringBuilder, as it processes the elements in the stream. If we wanted to take a stream of strings and concatenate them into a single long string, we could achieve this with ordinary reduction:

String concatenated = strings.reduce("", String::concat)

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#MutableReduction

So, in a nutshell, it works because behind the scenes java is using a stringbuilder

Related