Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java.util.stream.Collectors: Why is the summingInt implemented with an array?

The standard Collector summingInt internally creates an array of length one:

public static <T> Collector<T, ?, Integer>
summingInt(ToIntFunction<? super T> mapper) {
    return new CollectorImpl<>(
            () -> new int[1],
            (a, t) -> { a[0] += mapper.applyAsInt(t); },
            (a, b) -> { a[0] += b[0]; return a; },
            a -> a[0], CH_NOID);
}

I was wondering if it isn't possible to just define:

private <T> Collector<T, Integer, Integer> summingInt(ToIntFunction<? super T> mapper) {
    return Collector.of(
            () -> 0,
            (a, t) -> a += mapper.applyAsInt(t),
            (a, b) -> a += b,
            a -> a
    );
}

This however doesn't work since the accumulator just seems to be ignored. Can anyone explain this behaviour?

like image 362
PeMa Avatar asked Jul 26 '19 12:07

PeMa


People also ask

What is the use of @collectors in Java stream?

Collectors Class in Java Stream API is an implementation of Collector interface and provides many useful operations that can be used with Java Streams. One such group of methods, which can be classified as Collectors.summing methods, is used to add the stream elements.

How do you sum INTs in Java 8 streams?

Java 8 Streams - Collectors.summingInt Examples. The static method, Collectors.summingInt() returns a Collector which uses a provided mapper function to convert each input element of type T to primitive int, and returns the sum of the resulting integer values.

How to get a sequential stream from an array in Java?

import java.util.stream.*; The stream (T [] array, int startInclusive, int endExclusive) method of Arrays class in Java, is used to get a Sequential Stream from the array passed as the parameter with only some of its specific elements. These specific elements are taken from a range of index passed as the parameter to this method.

What is a collect method in Java 8?

Collectors play an important role in Java 8 streams processing. They ‘collect’ the processed elements of the stream into a final representation. Invoking the collect () method on a Stream, with a Collector instance passed as a parameter ends that Stream’s processing and returns back the final result.


1 Answers

An Integer is immutable, while an Integer[] array is mutable. An accumulator is supposed to be stateful.


Imagine you've got 2 references to 2 Integer objects.

Integer a = 1;
Integer b = 2;

By nature, the instances you are referring to are immutable: you can't modify them once they have been created.

Integer a = 1;  // {Integer@479}
Integer b = 2;  // {Integer@480}

You've decided to use a as an accumulator.

a += b; 

The value a is currently holding satisfies you. It's 3. However, a no longer refers to that {Integer@479} you used to have at the beginning.

I added debug statements to your Collector and make things clear.

public static  <T> Collector<T, Integer, Integer> summingInt(ToIntFunction<? super T> mapper) {
  return Collector.of(
      () -> {
        Integer zero = 0;
        System.out.printf("init [%d (%d)]\n", zero, System.identityHashCode(zero));
        return zero;
      },
      (a, t) -> {
        System.out.printf("-> accumulate [%d (%d)]\n", a, System.identityHashCode(a));
        a += mapper.applyAsInt(t);
        System.out.printf("<- accumulate [%d (%d)]\n", a, System.identityHashCode(a));
      },
      (a, b) -> a += b,
      a -> a
  );
}

If you use it, you'll notice a pattern like

init [0 (6566818)]
-> accumulate [0 (6566818)]
<- accumulate [1 (1029991479)]
-> accumulate [0 (6566818)]
<- accumulate [2 (1104106489)]
-> accumulate [0 (6566818)]
<- accumulate [3 (94438417)]

where 0 (6566818) is not being changed despite all abortive attempts with +=.

If you rewrote it to using an AtomicInteger

public static  <T> Collector<T, AtomicInteger, AtomicInteger> summingInt(ToIntFunction<? super T> mapper) {
  return Collector.of(
      () -> {
        AtomicInteger zero = new AtomicInteger();
        System.out.printf("init [%d (%d)]\n", zero.get(), System.identityHashCode(zero));
        return zero;
      },
      (a, t) -> {
        System.out.printf("-> accumulate [%d (%d)]\n", a.get(), System.identityHashCode(a));
        a.addAndGet(mapper.applyAsInt(t));
        System.out.printf("<- accumulate [%d (%d)]\n", a.get(), System.identityHashCode(a));
      },
      (a, b) -> { a.addAndGet(b.get()); return a;}
  );
}

you would be seeing a true accumulator (as a part of mutable reduction) in action

init [0 (1494279232)]
-> accumulate [0 (1494279232)]
<- accumulate [1 (1494279232)]
-> accumulate [1 (1494279232)]
<- accumulate [3 (1494279232)]
-> accumulate [3 (1494279232)]
<- accumulate [6 (1494279232)]
like image 132
Andrew Tobilko Avatar answered Oct 24 '22 08:10

Andrew Tobilko