我想知道是否有更好(或只是另一种)方法来获取进入流终端操作的所有项目的计数,而不是以下内容:
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
到位,我可以对到目前为止所有已处理的项目进行计数。然而,这并不是一个硬性要求。
在终端操作 IMO 之前,您似乎已经通过peek
获得了最干净的解决方案。我认为需要这样做的唯一原因是出于调试目的 - 如果是这种情况,那么peek
就是为此而设计的。为此包装流并提供单独的实现太多了 - 除了大量的时间和后来对添加到Streams
中的所有内容的支持。
对于如果添加了另一个过滤器怎么办?好吧,提供一个代码注释(我们很多人都这样做)和一些测试用例,否则这些用例会失败。
只是我的0.02美元
最好的想法是在自身上使用映射,并在这样做时计算映射例程的调用。
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 值并且不是线程安全的,因此在并行流设置中,人们会用AtomicInteger
实例替换int
。