Java 8 Streams:计算进入终端操作的所有元素

时间:2017-04-27 09:23:23

标签: java java-8 java-stream

我想知道是否有更好的(或者只是其他)方法来获取进入流的终端操作而不是以下内容的所有项目的计数:

Stream<T> stream = ... // given as parameter
AtomicLong count = new AtomicLong();
stream.filter(...).map(...)
      .peek(t -> count.incrementAndGet())

其中count.get()给出了该阶段处理项目的实际数量。

我故意跳过终端操作,因为这可能会在.forEach.reduce.collect之间发生变化。 我确实知道.count,但只有当我将.forEach.map交换并使用.count作为终端操作时,它似乎才能正常工作。但在我看来好像.map被误用了。

我不喜欢上述解决方案:如果在其后添加过滤器,它只计算该特定阶段的元素,而不是进入终端操作的元素。

我想到的另一种方法是将已过滤和映射的值collect放入列表并对其进行操作,然后调用list.size()来获取计数。但是这不起作用,如果流的集合会导致错误,而使用上述解决方案,我可以计算到目前为止所有已处理的项目,如果适当的try/catch到位。然而,这并不是一项艰难的要求。

2 个答案:

答案 0 :(得分:6)

在终端操作IMO之前,您似乎已经通过peek获得了最干净的解决方案。我认为这是需要的唯一原因是出于调试目的 - 如果是这样的话,那么peek就是为此而设计的。为此包装流并提供单独的实现是太多了 - 除了大量的时间和以后支持所有被添加到Streams的内容。

对于的部分,如果添加了另一个过滤器怎么办?那么,提供一个代码注释(我们很多人这样做)和一些测试用例,否则会失败,例如。

只是我的0.02美元

答案 1 :(得分:0)

最好的想法是使用自身的映射,同时计算映射例程的调用。

steam.map(object -> {counter.incrementAndGet(); return object;});

由于这个lambda可以重用,你可以用对象替换任何lambda,你可以创建一个这样的计数器对象:

class StreamCounter<T> implements Function<? super T,? extends T> {
  int counter = 0;
  public T apply(T object) { counter++; return object;}
  public int get() { return counter;}
}

所以使用:

StreamCounter<String> myCounter = new ...;
stream.map(myCounter)...
int count = myCounter.get();

由于地图调用再次只是另一个重用点,因此可以通过扩展Stream并包装普通流来提供map方法。

这样你可以创建类似的东西:

AtomicLong myValue = new AtomicLong();
...
convert(stream).measure(myValue).map(...).measure(mySecondValue).filter(...).measure(myThirdValue).toList(...);

通过这种方式,您可以简单地拥有自己的Stream包装器,该包装器在其自己的版本中透明地包装每个流(这不是性能或内存开销)并测量任何此类测量点的基数。

这通常在创建map / reduce解决方案时分析算法的复杂性时完成。通过不使用原子长实例进行计数来扩展流实现,但只有流量实现的度量点名称可以包含无限数量的度量点,同时提供灵活的打印报告方式。

这样的实现可以记住流方法的具体顺序以及每个测量点的位置,并带来如下输出:

list ->  (32k)map -> (32k)filter -> (5k)map -> avg(). 

此类流实现只编写一次,可用于测试,也可用于报告。

内置于每天的实现中,可以收集某些处理的统计信息,并通过使用不同的操作排列来实现动态优化。例如,这将是一个查询优化器。

因此,在您的情况下,最好的方法是首先重用StreamCounter,并且根据使用频率,计数器的数量和DRY原则的亲和力最终会在以后实施更复杂的解决方案。

PS:StreamCounter使用int值并且不是线程安全的,因此在并行流设置中,可以用int实例替换AtomicInteger