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

Posted

技术标签:

【中文标题】如何将多个谓词应用于 java.util.Stream?【英文标题】:How to apply multiple predicates to a java.util.Stream? 【发布时间】:2014-08-24 13:56:29 【问题描述】:

如何将多个谓词应用于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());

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

【问题讨论】:

possible duplicate @A4L:这不是重复的,我问的是当你不知道会有多少过滤器时如何使用多个过滤器。如果只是一两,我已经知道答案了。 您可以在循环中使用.or 构建复合过滤器。 如何创建谓词列表?你不能用Predicate&lt;Thing&gt; allPredicates之类的东西代替allPredicates.and(predicate);allPredicates.or(predicate);添加其他谓词,然后在filter中使用allPredicates吗? @Pshemo:这正是我要问的。如何即时创建?我将有一个由类的外部消费者创建的谓词列表,我需要创建某种超级谓词。这就是我实际所做的,但看起来不太好。 【参考方案1】:

如果您有Collection&lt;Predicate&lt;T&gt;&gt; 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-&gt;trueanding 谓词而x-&gt;falseoring 做)你也可以使用reduce(x-&gt;true, Predicate::and)reduce(x-&gt;false, Predicate::or) 来获取过滤器,但对于非常小的集合来说效率稍低,因为它总是将标识谓词与集合的谓词结合起来,即使它只包含一个谓词。相反,如果集合的大小为1,则上面显示的变体reduce(accumulator).orElse(fallback) 将返回单个谓词。


请注意此模式如何也适用于类似问题:拥有Collection&lt;Consumer&lt;T&gt;&gt;,您可以使用Consumer&lt;T&gt; 创建单个Consumer&lt;T&gt;

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

等等

【讨论】:

为什么 OR 的后备 false 和 AND 的 true @lmichelbacher:因为(x | false) == x(x &amp; true) == x。这就是身份值的要求,就像x + 0 == xx * 1 == x一样。 有道理。谢谢。【参考方案2】:

我假设您的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)
);

【讨论】:

我不明白你想说“每个过滤器都在它自己的Predicate 后面”。术语“过滤器”和类型Predicate在这里具有相同的含义。 在写答案时,我假设 OP 有一些自定义的“过滤器”,它在形式上与 Predicate 的类型不同,因此需要一个转换步骤,将 OP 的过滤器包装成一个谓词,创建一个间接级别。如果确实是Filter implements Predicate,那么情况就简单多了。 我明白了。但我不认为 OP 的意图是引入一种新的谓词类型。整个问题的措辞中没有任何暗示。只有一个地方出现了Filter,我想这只是一个疏忽,无论如何都应该是Predicate 如果我从这个前提开始,我永远不会写出第一个解决方案,我想出这个解决方案只是因为我无法快速想出一个简洁的方法来适应 FilterPredicate . 也许最好在答案中指出问题的模棱两可,以明确在哪种情况下更喜欢哪种解决方案。我只是忽略了Filter 的一次出现,这就是为什么我对你的陈述感到困惑。但也许Filter 故意的,谁(除了OP)知道......【参考方案3】:

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

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

【讨论】:

【参考方案4】:

这是解决此问题的一种有趣方法,(直接从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);

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

【讨论】:

恐怕它并不能解决问题——你知道谓词的数量。当我不知道谓词的数量(可能是无限的)时,我询问了这种情况。 我同意.. 但是我们可以创建一个循环并使用 OR(或 AND)将它们链接起来

以上是关于如何将多个谓词应用于 java.util.Stream?的主要内容,如果未能解决你的问题,请参考以下文章

使用谓词查询核心数据

在每个谓词 scala 处将字符串列表拆分为多个列表

如何在不同的数据上使用 NSPredicate 和 NSPredicateEditor(多个谓词?)

NSPredicate,CoreData 中的空格。如何修剪谓词?

你如何构造 NSFetchRequest setHavingPredicate: 的谓词?

具有多个谓词的 C++11 算法