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

问题描述 投票:0回答:5

如何将多个谓词应用于

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());

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

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

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

java lambda java-8 java-stream predicate
5个回答
68
投票

如果您有一个

Collection<Predicate<T>> filters
,您始终可以使用称为 reduction 的过程从中创建单个谓词:

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

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

取决于您想要如何组合过滤器。

如果

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->{});

等等


55
投票

我假设你的

Filter
是一种与
java.util.function.Predicate
不同的类型,这意味着它需要适应它。一种可行的方法如下:

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

为每个谓词评估重新创建过滤器流会导致轻微的性能影响。为了避免这种情况,您可以将每个过滤器包装成

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)
);

15
投票

我已经成功解决了这样一个问题,如果用户想要在一个过滤操作中应用一系列谓词,一个可以是动态且未给定的列表,应该将其简化为一个谓词 - 像这样:

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”结合,reduce 行应该是:

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

10
投票

这是解决这个问题的一种有趣的方法,(直接从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 是谓词列表。我从中创建了一个谓词 predicateToIgnore。

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

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


0
投票

如果我的“过滤器”列表有 60000 条记录,则会出现内存不足错误 谓词 pred=filters.stream().reduce(Predicate::and).orElse(x->true);

你能帮我吗

© www.soinside.com 2019 - 2024. All rights reserved.