javalambdajava-8java-streampredicate

How to apply multiple predicates to a java.util.Stream?


How can I apply multiple predicates to a java.util.Stream's filter() method?

This is what I do now, but I don't really like it. I have a Collection of things, and I need to reduce the number of things based on the Collection of filters (predicates):

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

I know that if I knew number of filters up-front, I could do something like this:

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

But how can I apply an unknown number of predicates without mixing programming styles? For now, it looks sort of ugly...


Solution

  • I am assuming your Filter is a type distinct from java.util.function.Predicate, which means it needs to be adapted to it. One approach which will work goes like this:

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

    This incurs a slight performance hit of recreating the filter stream for each predicate evaluation. To avoid that you could wrap each filter into a Predicate and compose them:

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

    However, since now each filter is behind its own Predicate, introducing one more level of indirection, it is not clear-cut which approach would have better overall performance.

    Without the adapting concern (if your Filter happens to be a Predicate) the problem statement becomes much simpler and the second approach clearly wins out:

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