如何将多个 predicate 应用于java.util.Stream?

ngynwnxp  于 2023-06-20  发布在  Java
关注(0)|答案(4)|浏览(123)

如何将多个 predicate 应用于java.util.Stream'sfilter()方法?
这就是我现在做的,但我真的不喜欢它。我有一个Collection的东西,我需要减少基于Collection的过滤器( predicate )的东西的数量:

Collection<Thing> things = someGenerator.someMethod();
List<Thing> filtered = things.parallelStream().filter(p -> {
   for (Filter f : filtersCollection) {
      if (f.test(p))
        return true;
   }
   return false;
}).collect(Collectors.toList());

我知道,如果我事先知道过滤器的数量,我可以做这样的事情:

List<Thing> filtered = things.parallelStream().filter(filter1).or(filter2).or(filter3)).collect(Collectors.toList());

但是,如何在不混合编程风格的情况下应用未知数量的 predicate 呢?现在,它看起来有点丑…

67up9zun

67up9zun1#

如果你有一个Collection<Predicate<T>> filters,你总是可以使用名为 reduction 的过程来创建一个 predicate :

Predicate<T> pred=filters.stream().reduce(Predicate::and).orElse(x->true);

Predicate<T> pred=filters.stream().reduce(Predicate::or).orElse(x->false);

这取决于你想如何合并过滤器。
如果orElse调用中指定的空 predicate 集合的回退满足标识角色(x->trueand执行的操作,x->falseor执行的操作)您也可以使用reduce(x->true, Predicate::and)reduce(x->false, Predicate::or)来获取过滤器,但这会导致对于非常小的集合,它的效率稍低,因为它总是将标识 predicate 与集合的 predicate 组合在一起,即使它只包含一个 predicate 。相比之下,如果集合的大小为1,则上面显示的变体reduce(accumulator).orElse(fallback)将返回单个 predicate 。
请注意,此模式也适用于类似的问题:有了一个Collection<Consumer<T>>,您可以使用以下命令创建一个Consumer<T>

Consumer<T> c=consumers.stream().reduce(Consumer::andThen).orElse(x->{});

等等。

k2arahey

k2arahey2#

我假设你的Filter是一个不同于java.util.function.Predicate的类型,这意味着它需要适应它。一种可行的方法是这样的:

things.stream().filter(t -> filtersCollection.stream().anyMatch(f -> f.test(t)));

这会导致为每个 predicate 计算重新创建过滤器流的轻微性能下降。为了避免这种情况,您可以将每个过滤器 Package 成一个Predicate并将它们组合起来:

things.stream().filter(filtersCollection.stream().<Predicate>map(f -> f::test)
                       .reduce(Predicate::or).orElse(t->false));

然而,由于现在每个过滤器都在其自己的Predicate之后,引入了一个间接级别,因此不清楚哪种方法会具有更好的整体性能。
如果不考虑适应性(如果你的Filter恰好是Predicate),问题陈述会变得简单得多,第二种方法显然获胜:

things.stream().filter(
   filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
kt06eoxx

kt06eoxx3#

我已经设法解决了这样一个问题,如果用户想要在一个过滤操作中应用一个 predicate 列表,一个可以是动态的并且不给定的列表,那么应该减少到一个 predicate -像这样:

public class TestPredicates {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        System.out.println(numbers.stream()
                .filter(combineFilters(x -> x > 2, x -> x < 9, x -> x % 2 == 1))
                .collect(Collectors.toList()));
    }

    public static <T> Predicate<T> combineFilters(Predicate<T>... predicates) {

        Predicate<T> p = Stream.of(predicates).reduce(x -> true, Predicate::and);
        return p;

    }
}

注意,这将把它们与一个“AND”逻辑运算符组合在一起。要与“OR”合并,减少线应该是:

Predicate<T> p = Stream.of(predicates).reduce(x -> false, Predicate::or);
3pvhb19x

3pvhb19x4#

这是解决这个问题的一个有趣的方法,(直接从http://www.leveluplunch.com/java/tutorials/006-how-to-filter-arraylist-stream-java8/粘贴)。我认为这是一种更有效的方法。

Predicate<BBTeam> nonNullPredicate = Objects::nonNull;
Predicate<BBTeam> nameNotNull = p -> p.teamName != null;
Predicate<BBTeam> teamWIPredicate = p -> p.teamName.equals("Wisconsin");

Predicate<BBTeam> fullPredicate = nonNullPredicate.and(nameNotNull)
        .and(teamWIPredicate);

List<BBTeam> teams2 = teams.stream().filter(fullPredicate)
        .collect(Collectors.toList());

编辑:这里是如何处理循环,其中predicatesToIgnore是一个 predicate 列表。我从它创建一个 predicate predicate ToIgnore。

Predicate<T> predicateToIgnore = null;
for (Predicate<T> predicate : predicatesToIgnore) {
    predicateToIgnore = predicateToIgnore == null ? predicate : predicateToIgnore.or(predicate);
}

然后,用这个 predicate 进行过滤。这创造了一个更好的过滤IMHO

相关问题