0

我想处理一个列表,然后计算处理了多少项目。我的代码是这样的:

List<String> names = ...;
names.stream().filter(...).map(...) /* a list of time-consuming filters and maps */
        .forEach(...);
// Then count them:
int count = names.stream().filter(...).map(...).count();

如果我想像这样简化我的代码使用供应商:复制流以避免“流已被操作或关闭”,我仍然需要执行两次耗时的过滤器和映射列表

所以我必须以一种丑陋的方式来做,比如:

List<String> filteredNames = names.stream().filter(...).map(...).collector(Collectors.toList());
filteredNames.forEach(...);
int count = filteredNames.size();

这可能会在 Java 堆上生成另一个巨大的列表。

一个更丑陋但有效的方法是:

int[] count = {0}; // or use AtomicInteger(0);
names.stream().filter(...).map(...).forEach(s -> {
    // process
    count[0] ++;
});

有没有一种优雅的方式来做到这一点?

4

3 回答 3

3

迭代 aList本身并不昂贵。重复 Stream 操作的费用取决于实际重复的中间操作。由于优化环境,代码在执行时,两个单一用途的循环可能会比将两个关注点放在一个循环中更有效。

请注意,计数不依赖于map操作的结果,因此,您可以在计数操作中省略它:

List<String> names = ...;
names.stream().filter(...).map(...).forEach(...);
// Then count them:
long count = names.stream().filter(...).count();

这是尝试和测量的变体,用于与同时执行两个操作的 Stream 方法进行比较。

请注意,这forEach并不能保证您的操作按顺序执行,并且在并行流的情况下,它甚至可以同时进行评估。类似的行为将适用于peek,以及将实际操作插入map函数的任何尝试。

尝试使用类似的东西

long count = names.stream().filter(...).map(...)
    .map(x -> {                                   // don't do this
        // your action
        return x;
    }).count();

承担额外的问题,即实现可能会考虑与上述相同的事情,map函数的结果与计数无关,因此即使当前实现没有,它也可能被跳过。

同样,使用

long count = names.stream().filter(...).map(...)
    .peek(x -> {                                  // don't do this
        // your action
    }).count();

承担一定的风险。最后map一步与 final 无关count,因此它可能会被跳过,在这种情况下,peek动作也必须被跳过,因为它的输入不存在。不能保证过时的中间操作只是为了操作而保留peek。我们只能推测,实施者将在多大程度上利用这一方面。但是当前参考实现中唯一实际的例子是,当计数是预先可预测的时跳过整个管道,这也跳过了所有peek操作。这表明我们不应该依赖peek行动的执行。

但是当你使用

int count = names.stream().filter(...).map(...)
    .mapToInt(x -> {                   // don't assume sequential in-order execution
        // your action
        return 1;
    }).sum();

最终结果取决于包含该操作的函数,因此将始终对其进行评估。但与forEach, 可以替换为forEachOrdered, 以获得保证的非并发、有序执行不同,映射函数没有这样的选项。

于 2020-06-28T13:58:07.503 回答
2

这并不是特别优雅或高效,但它是您未列出的替代方案(可变缩减):

AtomicInteger count = names.stream().collect(
        AtomicInteger::new,
        (sum, name) -> {
            //forEach action here
            sum.incrementAndGet();
        }, 
        (n1, n2) -> {
            n1.addAndGet(n2.get());
        });
于 2020-06-24T07:39:11.973 回答
0

如果您Stream无论如何都必须处理所有元素(看起来您这样做了,因为您正在使用forEach),为什么不在处理之前将它们收集到 aList中呢?然后你可以很容易地得到List.

List<String> names = ...;
List<Something> result = names.stream()
                              .filter(...)
                              .map(...) 
                              .collect(Collectors.toList());
result.forEach (...);
// Then count them:
int count = result.size ();
于 2020-06-24T07:19:18.263 回答