I'm an old Java programmer, and trying to properly learn streams, while doing some testing with the reduce function and StringBuffer/StringBuilder I found some behavior I don't quite understand.
This is the first example, it works as expected:
System.out.println(List.of("a", "b", "c", "d").stream().reduce(new StringBuilder(), (s, e)->s.append(e) , (sb1, sb2)->sb1==sb2?sb1:sb1.append(sb2)));
this is the second example, using parallel streams and StringBuffer(thread safe)
System.out.println(List.of("a", "b", "c", "d").parallelStream().reduce(new StringBuffer(), (s, e)->s.append(e) , (sb1, sb2)->sb1==sb2?sb1:sb1.append(sb2)));
now the thing is: the combiner checks to see if it's receiving the same object on both sides, if so it returns the left operand (they are the same object), and if they are not, it combines them and returns them: (sb1, sb2)->sb1==sb2?sb1:sb1.append(sb2)
but so far I always have the same StringBuffer object in all the threads, I added more data, but still receive the same object.
I switched to a Supplier and added a sequence at the creation of the object, but my runs always use the same one regardless.
var list = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z");
var counter = new HashMap<String, Integer>();
Supplier<StringBuffer> supplier = ()->{
var s = new StringBuffer();
s.append(counter.merge("c", 1, (a, b)->a+b));
System.out.printf("new:%s%n", s);
return s;
};
System.out.println(list.parallelStream().reduce(supplier.get(), (s, e)->s.append(e) , (sb1, sb2)->{System.out.printf("called with[%s] and [%s]%n", sb1, sb2);return sb1==sb2?sb1:sb1.append(sb2);}));
Same Object on the left and right side:
called with[1qsr] and [1qsr]
called with[1qsr] and [1qsr]
called with[1qsrhi] and [1qsrhi]
called with[1qsrhigno] and [1qsrhignopuv]
called with[1qsrhignopuvlm] and [1qsrhignopuvlm]
called with[1qsrhignopuvlmkjt] and [1qsrhignopuvlmkjt]
called with[1qsrhignopuvlmkjtxwzy] and [1qsrhignopuvlmkjtxwzydfe]
called with[1qsrhignopuvlmkjtxwzydfebc] and [1qsrhignopuvlmkjtxwzydfebc]
called with[1qsrhignopuvlmkjtxwzydfebc] and [1qsrhignopuvlmkjtxwzydfebc]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
called with[1qsrhignopuvlmkjtxwzydfebca] and [1qsrhignopuvlmkjtxwzydfebca]
So why isn't every thread creating a different instance of the accumulator object?
You haven't told it how to. You just passed it a single StringBuffer, it doesn't know how to make more.
This reduce
function isn't intended or designed to be able to handle this case, where you have a mutable accumulator. For this, you'll need a Collector
, and to use stream.collect(customCollector)
. The simplest implementation for this would be
Collector.of(StringBuilder::new, StringBuilder::append, StringBuilder::append)
or if you want a String
at the end
Collector.of(
StringBuilder::new, StringBuilder::append,
StringBuilder::append, StringBuilder::toString)