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

Paweł Dyda picture Paweł Dyda · Jul 3, 2014 · Viewed 56k times · Source

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 unknown number of predicates without mixing programming styles? For know it looks sort of ugly...

Answer

Holger picture Holger · Jul 3, 2014

If you have a Collection<Predicate<T>> filters you can always create a single predicate out of it using the process called reduction:

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

or

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

depending on how you want to combine the filters.

If the fallback for an empty predicate collection specified in the orElse call fulfills the identity role (which x->true does for anding the predicates and x->false does for oring) you could also use reduce(x->true, Predicate::and) or reduce(x->false, Predicate::or) to get the filter but that’s slightly less efficient for very small collections as it would always combine the identity predicate with the collection’s predicate even if it contains only one predicate. In contrast, the variant reduce(accumulator).orElse(fallback) shown above will return the single predicate if the collection has size 1.


Note how this pattern applies to similar problems as well: Having a Collection<Consumer<T>> you can create a single Consumer<T> using

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

Etc.