【问题标题】:java.util.stream.Collectors: Why is the summingInt implemented with an array?java.util.stream.Collectors:为什么 summingInt 是用数组实现的?
【发布时间】:2019-07-26 12:06:05
【问题描述】:

标准 Collector 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);
}

我想知道是否不能只定义:

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
    );
}

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

【问题讨论】:

    标签: java java-8 java-stream collectors


    【解决方案1】:

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


    假设您有 2 个对 2 个 Integer 对象的引用。

    Integer a = 1;
    Integer b = 2;
    

    本质上,您所指的实例是不可变的:它们一旦创建就无法修改。

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

    您决定使用a 作为累加器。

    a += b; 
    

    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
      );
    }
    

    如果你使用它,你会注意到类似的模式

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

    尽管+= 的所有尝试都失败了,但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;}
      );
    }
    

    您将看到一个真正的累加器(作为mutable reduction 的一部分)在运行

    init [0 (1494279232)]
    -> accumulate [0 (1494279232)]
    <- accumulate [1 (1494279232)]
    -> accumulate [1 (1494279232)]
    <- accumulate [3 (1494279232)]
    -> accumulate [3 (1494279232)]
    <- accumulate [6 (1494279232)]
    

    【讨论】:

    • 你能否解释一下,这在代码方面是可读的,或者至少给出一个提示?
    • 你为什么说Integer[],而问题中的代码使用int[]
    • 不可变与否,关键是(a, b) -&gt; a += b改变了参数,没有影响,因为Java是按值调用的。在累加器(BiConsumer)的情况下,(a, b) -&gt; a += b 之类的函数没有持久效果,而在组合器(BinaryOperator)的情况下,使用表达式的结果,所以(a, b) -&gt; a += b将具有与(a, b) -&gt; a + b 相同的效果。无论哪种情况,更改参数都是没有意义的。
    猜你喜欢
    • 2011-08-23
    • 2019-06-17
    • 2017-07-25
    • 2012-08-16
    • 2017-12-21
    • 2019-12-03
    • 2011-03-01
    • 2011-09-03
    相关资源
    最近更新 更多