java.util.stream.Collectors:为什么使用数组实现summingInt?

PeM*_*eMa 8 java java-8 java-stream collectors

标准收集器在summingInt内部创建一个长度为1的数组:

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);
}
Run Code Online (Sandbox Code Playgroud)

我想知道是否不可能仅定义:

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
    );
}
Run Code Online (Sandbox Code Playgroud)

但是,这不起作用,因为似乎只是忽略了累加器。谁能解释这种行为?

And*_*lko 10

an Integer是不可变的,而Integer[]数组是可变的。一个累加器应该是有状态的。


想象一下,您有2个对2个Integer对象的引用。

Integer a = 1;
Integer b = 2;
Run Code Online (Sandbox Code Playgroud)

从本质上讲,您引用的实例是不可变的:创建实例后就无法对其进行修改。

Integer a = 1;  // {Integer@479}
Integer b = 2;  // {Integer@480}
Run Code Online (Sandbox Code Playgroud)

您已决定a用作累加器。

a += b; 
Run Code Online (Sandbox Code Playgroud)

该值a当前保持令人满意。是3。但是,a不再指的是{Integer@479}您一开始就曾经拥有的。

我向您添加了调试语句Collector,并使事情变得清晰。

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
  );
}
Run Code Online (Sandbox Code Playgroud)

如果使用它,您会注意到类似

init [0 (6566818)]
-> accumulate [0 (6566818)]
<- accumulate [1 (1029991479)]
-> accumulate [0 (6566818)]
<- accumulate [2 (1104106489)]
-> accumulate [0 (6566818)]
<- accumulate [3 (94438417)]
Run Code Online (Sandbox Code Playgroud)

0 (6566818)尽管使用进行了所有失败的尝试,但哪里都没有更改+=

如果改写为使用 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;}
  );
}
Run Code Online (Sandbox Code Playgroud)

您会看到一个真正的累加器(作为可变减少的一部分)在起作用

init [0 (1494279232)]
-> accumulate [0 (1494279232)]
<- accumulate [1 (1494279232)]
-> accumulate [1 (1494279232)]
<- accumulate [3 (1494279232)]
-> accumulate [3 (1494279232)]
<- accumulate [6 (1494279232)]
Run Code Online (Sandbox Code Playgroud)

  • 关键点是,((a,b)-&gt; a + = b`更改参数,这是无效的,因为Java是按值调用。在累加器为BiConsumer的情况下,像(a,b)-&gt; a + = b的函数没有持久影响,而在组合器为BinaryOperator的情况下,结果为使用表达式的,因此`(a,b)-&gt; a + = b`将具有与`(a,b)-&gt; a + b`相同的效果。无论哪种情况,更改参数都是没有意义的。 (4认同)