Unexpected result calling collect method on a Java Stream

91 views Asked by At

I am trying to understand this Java Stream method

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

The following code should give as a result 55 put instead 0 is printed out. Can someone explain to me what is wrong and how to change it so that 55 is printed?

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Create a supplier that returns a new mutable result container, in this case an Integer accumulator.
Supplier<Integer> supplier = () -> 0;

// Create an accumulator that adds the square of the current element to the result container.
BiConsumer<Integer, Integer> accumulator = (result, element) -> result += element * element;

// Create a combiner that adds two result containers together.
BiConsumer<Integer, Integer> combiner = (result1, result2) -> result1 += result2;

// Collect the results of the reduction operation into a single Integer value.
Integer sumOfSquares = numbers.stream()
                              .collect(supplier, accumulator, combiner);

System.out.println(sumOfSquares); // 55
3

There are 3 answers

1
berse2212 On BEST ANSWER

You are working on ints/Integer which are not mutable. Thus both, the accumulator and the combiner don't alter the result. Thus is stays 0.

You can avoid this problem by using a mutable object like AtomicInteger to do the operations and use a finisher to convert it back to Integer:

public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

    // Create a supplier that returns a new mutable! result container, in this case an AtomicInteger accumulator.
    Supplier<AtomicInteger> supplier = () -> new AtomicInteger(0);

    // Create an accumulator that adds the square of the current element to the result container.
    BiConsumer<AtomicInteger, Integer> accumulator = (result, element) -> result.getAndAdd(element * element);

    // Create a combiner that adds two result containers together.
    BinaryOperator<AtomicInteger> combiner = (result1, result2) -> {
        result1.getAndAdd(result2.get());
        return result1;
    };

    //Create a Finisher to get the Integer value
    Function<AtomicInteger, Integer> finisher = AtomicInteger::get;

    // Collect the results of the reduction operation into a single Integer value.
    Integer sumOfSquares = numbers.stream().collect(Collector.of(supplier, accumulator, combiner, finisher));

    System.out.println(sumOfSquares); // 55
}
0
softwareguy On

The issue in your code lies in the accumulator and combiner functions. In Java, the BiConsumer interface does not allow modifications to its parameters. When you use result += element * element or result1 += result2, you're trying to modify the values passed to the functions, which won't affect the original values.

To fix this, you need to use a mutable wrapper for your integer values. One way to do this is by using AtomicInteger. Here's how you can modify your code:

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        Supplier<AtomicInteger> supplier = () -> new AtomicInteger(0);

        BiConsumer<AtomicInteger, Integer> accumulator = (result, element) -> result.addAndGet(element * element);

        BiConsumer<AtomicInteger, AtomicInteger> combiner = (result1, result2) -> result1.addAndGet(result2.get());

        AtomicInteger sumOfSquares = numbers.stream().collect(supplier, accumulator, combiner);

        System.out.println(sumOfSquares); 
    }
}
3
Abra On

I assume that the below code is not the answer you were seeking, nonetheless I believe it is a viable alternative solution.

  • The Stream<Integer> is converted to an IntStream.
  • Each element of the IntStream is squared.
  • All the [squared] elements are summed.
numbers.stream()
       .mapToInt(n -> n.intValue())
       .map(e -> e * e)
       .sum();

The result is an int which you can convert to Integer, for example via [static] method valueOf in class java.lang.Integer.