虽然在行为参数中使用副作用是不鼓励的,但只要没有干扰,它们并不被禁止,因此最简单的解决方案(虽然不是最干净的)是在过滤器中直接计数:
AtomicInteger rejected=new AtomicInteger();
Files.lines(path)
.filter(line -> {
boolean accepted=isTypeA(line);
if(!accepted) rejected.incrementAndGet();
return accepted;
})
只要您处理所有项,结果就会保持一致。仅当您使用短路终端操作(在并行流中)时,结果才会变得不可预测。
更新原子变量可能不是最有效的解决方案,但在从文件处理行的上下文中,开销可能微不足道。
如果您想要一个干净、并行友好的解决方案,一个通用的方法是实现一个收集器,该收集器可以基于条件结合两个收集操作的处理。这需要您能够将下游操作表达为收集器,但大多数流操作都可以表达为收集器(而且趋势正在朝着可能表达所有操作的方式发展,即Java 9将添加当前缺失的过滤和平铺映射)。
您需要一种成对类型来保存两个结果,因此假设有一个草图。
class Pair<A,B> {
final A a;
final B b;
Pair(A a, B b) {
this.a=a;
this.b=b;
}
}
组合收集器的实现将如下所示:
public static <T, A1, A2, R1, R2> Collector<T, ?, Pair<R1,R2>> conditional(
Predicate<? super T> predicate,
Collector<T, A1, R1> whenTrue, Collector<T, A2, R2> whenFalse) {
Supplier<A1> s1=whenTrue.supplier();
Supplier<A2> s2=whenFalse.supplier();
BiConsumer<A1, T> a1=whenTrue.accumulator();
BiConsumer<A2, T> a2=whenFalse.accumulator();
BinaryOperator<A1> c1=whenTrue.combiner();
BinaryOperator<A2> c2=whenFalse.combiner();
Function<A1,R1> f1=whenTrue.finisher();
Function<A2,R2> f2=whenFalse.finisher();
return Collector.of(
()->new Pair<>(s1.get(), s2.get()),
(p,t)->{
if(predicate.test(t)) a1.accept(p.a, t); else a2.accept(p.b, t);
},
(p1,p2)->new Pair<>(c1.apply(p1.a, p2.a), c2.apply(p1.b, p2.b)),
p -> new Pair<>(f1.apply(p.a), f2.apply(p.b)));
}
比如,可以将匹配项收集到列表中并计算不匹配项的数量,像这样:
Pair<List<String>, Long> p = Files.lines(path)
.collect(conditional(line -> isTypeA(line), Collectors.toList(), Collectors.counting()))
List<String> matching=p.a
long nonMatching=p.b
收集器对并行处理友好,并允许任意复杂的委托收集器,但请注意,使用当前实现,
Files.lines
返回的流在并行处理时可能性能不佳,与 {{link1:“Reader#lines()由于其分割器中的不可配置批处理大小策略而无法并行化”}} 相比。改进计划在Java 9发布时进行。
partitioningBy
收集器,但需要一个临时 map 数据持有者。 - Tunaki