1

My question has to do with when one of the parameters in the reduce function of Streams is used, I will need to explain my setup first before to describe the problem more fully.

Suppose I have a Person class with String name and int age as fields, I make a few people in the following static method:

public static List<Person> createPeople() {
    return List.of(new Person("Sara", 20),
                   new Person("Sara", 22),
                   new Person("Bob", 20),
                   new Person("Paula", 32),
                   new Person("Paul", 32),
                   new Person("Jack", 3),
                   new Person("Jack", 72),
                   new Person("Jill", 11));
    }

Now, I want to get a List of the names of people over 30 years of age. I know I can achieve this using the collect function easily, but I wanted to explore one aspect of doing it with the reduce function. I have the following code:

List<String> namesOfOlderThan30 = createPeople().stream()
                                                .filter(person -> person.getAge() > 30)
                                                .map(Person::getName)
                                                .map(String::toUpperCase)
                                                .reduce(new ArrayList<String>(), 
                                                (names, name) -> {
                                                        names.add(name);
                                                        System.out.println(names);
                                                        return names;
                                                 }, 
                                                (names1, names2) -> {
                                                 System.out.println("Before: "+ names1);
                                                 names1.addAll(names2);
                                                 System.out.println("After: "+ names1);
                                                 return names1;
                                           }); 

According to the source, I see that the method signature for reduce when there are three arguments is:

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

When I run this code above, I get the output:

[PAULA]
[PAULA, PAUL]
[PAULA, PAUL, JACK]

Since I coded each of the functions to print, this means that the BinaryOperator combiner parameter was not used at all.

However, when I run the same code using parallelStream() instead of just stream(), I get:

[JACK, PAUL]
Before: [JACK, PAUL, PAULA]
[JACK, PAUL, PAULA]
[JACK, PAUL]
After: [JACK, PAUL, PAULA, JACK, PAUL, PAULA]
Before: [JACK, PAUL, PAULA]
Before: [JACK, PAUL, PAULA, JACK, PAUL, PAULA]
After: [JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA]
Before: [JACK, PAUL, PAULA, JACK, PAUL, PAULA]
After: [JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA]
After: [JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA, JACK, PAUL, PAULA]
...
Exception in thread "main" java.util.ConcurrentModificationException: java.util.ConcurrentModificationException

This makes me think that the third parameter in the reduce method is related to some parallel function of streams, but I do not understand how it is related. Further, when I remove the lines for printing, the error no longer occurs when using parallelStream.

Can someone help explain what is happening here for me in more detail?

Thank you.

Richard K Yu
  • 2,152
  • 3
  • 8
  • 21
  • 1
    About the concurrent modification exception, `ArrayList` is not thread-safe, so you should not be doing things to it in parallel. Actually, I think this approach ("mutable reduction") of using `reduce` just won't work at all, even if you use a thread safe collection. The 3-parameter overload of `collect` exists for this very purpose, after all. See also: https://stackoverflow.com/questions/22577197/java-8-streams-collect-vs-reduce – Sweeper Dec 26 '21 at 16:28

0 Answers0