是否可以只迭代一次流并执行 2 个或更多操作?

Is it possible to iterate a stream only once and perform 2 or more operations?

给定代码

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

int sumTotal = numbers.stream().reduce(-3, (x, y) -> x + y + 3);
int multiplyTotal = numbers.stream().reduce(1, (x, y) -> x * y);

是否可以在仅迭代流一次的同时执行这两个操作?

另外,请注意每个 reduce 都有不同的标识:-3 和 1。

在 JS 中,一种解决方案是遍历对象而不是数字。例如:

  numbers.stream().reduce({sum:0,prod:1}, (memo, cur) ->
       memo.sum = memo.sum + cur + 3;
       memo.prod = memo.prod * cur;
       return memo;
  )

请注意,在您的示例中,x 是 "memo"(或累加器),y 是当前值。

同样的想法也应该适用于 Java。

您可以创建自定义 class,并使用可变缩减:

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

    Result result = numbers.stream().collect(Result::new, Result::consume, Result::combine);
    System.out.println("result = " + result);
}

private static class Result {
    private int sum = 0;
    private int product = 1;

    public void consume(int i) {
        sum += i + 3;
        product *= i;
    }

    public void combine(Result r) {
        // READ note below
        sum += r.sum + 3;
        product *= r.product;
    }

    @Override
    public String toString() {
        return "Result{" +
            "sum=" + sum +
            ", product=" + product +
            '}';
    }
}

但我怀疑你这样做是否会比你正在做的简单地迭代两次获得更多。

编辑:

请注意,上面的 combine() 方法在使用并行流时调用,将产生与顺序流不一致的结果。正如 Holger 在评论中提到的那样,问题是由于该操作不遵守归约的身份先决条件这一事实引起的:对具有身份的任何值 V 应用归约应该产生 V,但它产生 V + 3 以 0 作为标识。

确保不要使用并行流来计算该结果。

你可以使用我在 中写的配对收集器:

int[] result = numbers.stream().collect(
        pairing(
                Collectors.reducing(0, (x, y) -> x + y + 3),
                Collectors.reducing(1, (x, y) -> x * y),
                (sum, prod) -> new int[] { sum, prod }));

当然,您可以通过任何其他方式合并结果。将它们放入数组只是一个例子。

这个收集器在我的 StreamEx library (MoreCollectors.pairing). Alternatively you may use jOOL library: it has Tuple.collectors 方法中很容易获得,它以类似的方式工作。