Java 流:在链接上使用可选的 filter() 操作

Java stream: use optional filter() operations on chaining

注意:此问题java.util.Optional无关

在处理流时,我经常使用这样的逻辑:

 Stream<FooBar> stream = myInitialStream();
 if (needsFilter1) stream = stream.filter(c -> whatever1());
 if (needsFilter2) stream = stream.filter(c -> whatever2());
 ...
 return stream.collect(toList());

我想要实现的是使用链接将上面的代码转换为单个表达式。我发现这更具可读性和直接性。 到目前为止,我发现实现该目标的唯一方法是:

return myInitialStream()
       .filter(needsFilter1? c->whatever1() : c->true)
       .filter(needsFilter2? c->whatever2() : c->true)
       .collect(toList());

不过,这会对那些琐碎的 c->true lamda 进行不必要的调用,这可能会在扩展时产生一些性能成本。

所以我的问题是:是否有更好的方法来生成包含可选过滤的链式流表达式?

更新:也许我说得不够清楚,但我的问题的重点是找到一个单表达式解决方案。如果我必须使用多个语句(例如初始化一个谓词),我也可以使用我的问题的第一个代码块,它基本上做同样的事情。

使用 Predicate::and 根据条件链接谓词,返回一个新的谓词。

Predicate<FooBar> predicate = c -> whatever();

if (condition1) { predicate = predicate.and(c -> whatever1()); }
if (condition2) { predicate = predicate.and(c -> whatever2()); }

List<FooBar> dest = list.stream()
    .filter(predicate)
    .collect(Collectors.toList());

更新请求单个表达式。无论如何,您需要 映射条件到谓词的来源。对于数据结构 Map<Supplier<Boolean>, Predicate<Integer>>,其中键是决定是否应使用值 (Predicate<FooBar>) 的条件的 Supplier

通过将这些 PredicatesPredicate::and 链接起来,将映射的条目减少到新的 Predicate<FooBar>,为此它们的 Supplier<Boolean> returns true(条件有效)。

具备Map的条件:

Map<Supplier<Boolean>, Predicate<FooBar>> map = new HashMap<>();
map.put(() -> needsFilter1, c -> whatever1());
map.put(() -> needsFilter2, c -> whatever2());
...

这是一个 Stream 语句:

List<Integer> dest = list
        .stream()
        .filter(map.entrySet()                            // filter with a predicate ...
                .stream()
                .filter(e -> e.getKey().get())            // .. where a condition is 'true'
                .map(Entry::getValue)                     // .. get Predicates
                .reduce(i -> true, (l, r) -> l.and(r)))   // .. reduce them using AND
        .collect(Collectors.toList());               

我的解决方案有点晚了,无论如何我会把它留在这里。

我曾想过编写一个构建器来构造一个复杂的 Predicate,但最终得到了一个 class FilterCondition 和一个方法 FilterCondition.combine

Stream.of("123", "1", "12345", "", "12", "", "2")
    .filter(FilterCondition.<String>combine(
                FilterCondition.of(() -> true, s -> s.contains("3")),
                FilterCondition.of(() -> true, s -> s.contains("2")),
                FilterCondition.of(() -> false, s -> s.isEmpty())
            ).toPredicate())
    .collect(Collectors.toList());

静态导入 FilterCondition.ofFilterCondition.combine, 看起来会更好。

Stream.of("123", "1", "12345", "", "12", "", "2")
    .filter(combine(
                of(() -> true, s -> s.contains("3")),
                of(() -> true, s -> s.contains("2")),
                of(() -> false, String::isEmpty)
            ).toPredicate())
    .collect(Collectors.toList());

FilterCondition<T> 基本上是一个带有额外条件的 Predicate<T>,用于检查是否应应用 predicate

FilterCondition.combine 需要一些 FilterCondition 并组成一个组合。

class FilterCondition<T> {
    private final Supplier<Boolean> filterEnabled;
    private final Predicate<T> predicate;

    private FilterCondition(Supplier<Boolean> filterEnabled, Predicate<T> predicate) {
        this.filterEnabled = filterEnabled;
        this.predicate = predicate;
    }

    public static <T> FilterCondition<T> of(Supplier<Boolean> filterEnabled, Predicate<T> predicate) {
        return new FilterCondition<>(filterEnabled, predicate);
    }

    @SafeVarargs
    public static <T> FilterCondition<T> combine(FilterCondition<T>... conditions) {
        return new FilterCondition<>(
                () -> true,
                Arrays.stream(conditions).filter(i -> i.filterEnabled.get()).map(i -> i.predicate).reduce(Predicate::and).orElse(t -> true)
        );
    }

    public Predicate<T> toPredicate() {
        return predicate;
    }

}