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

Paw*_*yda 50 java lambda predicate java-8 java-stream

如何将多个谓词应用于java.util.Stream's filter()方法?

这就是我现在所做的,但我并不喜欢它.我有一些Collection东西,我需要根据Collection过滤器(谓词)减少事物的数量:

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());
Run Code Online (Sandbox Code Playgroud)

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

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

但是如何在不混合编程风格的情况下应用未知数量的谓词?知道它看起来有点难看......

Hol*_*ger 51

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

Predicate<T> pred=filters.stream().reduce(Predicate::and).orElse(x->true);
Run Code Online (Sandbox Code Playgroud)

要么

Predicate<T> pred=filters.stream().reduce(Predicate::or).orElse(x->false);
Run Code Online (Sandbox Code Playgroud)

取决于您希望如何组合过滤器.

如果在orElse调用中指定的空谓词集合的回退履行了标识角色(这x->true对于and谓词和x->false对于谓词的作用or),您也可以使用reduce(x->true, Predicate::and)reduce(x->false, Predicate::or)获取过滤器,但对于非常小的集合,它的效率稍低,因为它总是如此将标识谓词与集合的谓词结合起来,即使它只包含一个谓词.相反,reduce(accumulator).orElse(fallback)如果集合具有大小,则上面显示的变体将返回单个谓词1.


注意这种模式如何也适用于类似的问题:拥有一个Collection<Consumer<T>>你可以创建一个Consumer<T>使用

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

等等.

  • @lmichelbacher:因为`(x | false)== x`和`(x&true)== x`.这就是身份值的要求,就像`x + 0 == x`和`x*1 == x`. (3认同)

Mar*_*nik 44

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

things.stream().filter(t -> filtersCollection.stream().anyMatch(f -> f.test(t)));
Run Code Online (Sandbox Code Playgroud)

这会为每个谓词评估重新创建过滤器流带来轻微的性能损失.为了避免这种情况,你可以将每个过滤器包装成一个Predicate并组成它们:

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

但是,由于现在每个过滤器都落后于它自己Predicate,引入了一个更多级别的间接,因此不清楚哪种方法具有更好的整体性能.

如果没有适应性问题(如果你Filter恰好是a Predicate),问题陈述会变得更加简单,第二种方法显然会胜出:

things.stream().filter(
   filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
Run Code Online (Sandbox Code Playgroud)

  • 我没有得到你想说的"每个过滤器都在它自己的`Predicate`后面".术语"过滤器"和类型"谓词"在这里具有相同的含义. (2认同)
  • 在编写答案时,我假设 OP 有一些自定义的“过滤器”,它在形式上与“谓词”的类型不同,因此需要一个转换步骤,将 OP 的过滤器包装成谓词,从而创建一个间接级别。如果确实`Filter 实现了Predicate`,那么情况就简单得多。 (2认同)

Uzi*_*ies 8

我设法解决了这样一个问题,如果用户想要在一个过滤操作中应用一个谓词列表,一个可以是动态但没有给出的列表,应该减少为一个谓词 - 如下所示:

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;

    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,这将它们与"AND"逻辑运算符组合在一起.要与"OR"组合,reduce行应该是:

Predicate<T> p = Stream.of(predicates).reduce(x -> false, Predicate::or);
Run Code Online (Sandbox Code Playgroud)


Gun*_*h D 7

这是一种解决这个问题的有趣方法,(直接粘贴于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());
Run Code Online (Sandbox Code Playgroud)

编辑:这是如何处理谓词ToIgnore是谓词列表的循环.我从中创建了一个谓词predicateToIgnore.

Predicate<T> predicateToIgnore = null;
for (Predicate<T> predicate : predicatesToIgnore) {
    predicateToIgnore = predicateToIgnore == null ? predicate : predicateToIgnore.or(predicate);
}
Run Code Online (Sandbox Code Playgroud)

然后,使用此单个谓词执行过滤.这创建了更好的过滤器恕我直言

  • 我担心,它没有解决问题 - 你知道预测的数量.当我不知道谓词的数量时,我询问了这种情况(它可能是无限的). (2认同)
  • 我同意。。但是我们可以做一个循环,然后使用OR(或AND)将它们链接起来 (2认同)