Composing predicates in Stream’s filters – Functional style programming – extending API

184. Composing predicates in Stream’s filters

A predicate (basically, a condition) can be modeled as a boolean-valued function via java.util.function.Predicate functional interface. Its functional method is named test(T t) and returns a boolean.Applying predicates in a stream pipeline can be done via several stream intermediate operations but we are interested here only in the filter(Predicate p) operation. For instance, let’s consider the following class:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  public Car(String brand, String fuel, int horsepower) {
    this.brand = brand;
    this.fuel = fuel;
    this.horsepower = horsepower;
  }
 
  // getters, equals(), hashCode(), toString()
}

If we have a List<Car> and we want to express a filter that produces all the cars that are Chevrolets then we can start by defining the proper Predicate:

Predicate<Car> pChevrolets
  = car -> car.getBrand().equals(“Chevrolet”);

Next, we can use this Predicate in a stream pipeline as follows:

List<Car> chevrolets = cars.stream()              
  .filter(pChevrolets)
  .collect(Collectors.toList());

A Predicate can be negated in at least three ways. We can negate the condition via the logical not (!) operator:

Predicate<Car> pNotChevrolets
  = car -> !car.getBrand().equals(“Chevrolet”);

We can call the Predicate.negate() method:

Predicate<Car> pNotChevrolets = pChevrolets.negate();     

Or, we can call the Predicate.not() method:

Predicate<Car> pNotChevrolets = Predicate.not(pChevrolets);

No matter which of these three approaches you prefer, the following filter will produce all cars that are not Chevrolets:

List<Car> notChevrolets = cars.stream()              
  .filter(pNotChevrolets)              
  .collect(Collectors.toList());

In the previous examples, we have applied a single predicate in a stream pipeline. But, we can apply multiple predicates as well. For instance, we may want to express a filter that produces all the cars that are not Chevrolets and have at least 150 horsepower. For the first part of this composite predicate, we can arbitrarily use pChevrolets.negate(), while for the second part, we need the following Predicate:

Predicate<Car> pHorsepower
  = car -> car.getHorsepower() >= 150;

We can obtain a composite predicate by chaining the filter() calls as follows:

List<Car> notChevrolets150 = cars.stream()              
  .filter(pChevrolets.negate())
  .filter(pHorsepower)
  .collect(Collectors.toList());

But, more shortly and expressive is to rely on Predicate#and(Predicate<? super T> other) which applies the short-circuiting logical AND between two predicates. So, the previous example is better expressed as follows:

List<Car> notChevrolets150 = cars.stream()              
  .filter(pChevrolets.negate().and(pHorsepower))
  .collect(Collectors.toList());

If we need to apply the short-circuiting logical OR between two predicates then relying on Predicate#or(Predicate<? super T> other) is the proper choice. For instance, if we want to express a filter that produces all Chevrolets or electric cars then we can do it as follows:

Predicate<Car> pElectric
  = car -> car.getFuel().equals(“electric”);
      
List<Car> chevroletsOrElectric = cars.stream()              
  .filter(pChevrolets.or(pElectric))
  .collect(Collectors.toList());

If we are in a scenario that heavily relies on composite predicates then we can start by creating two helpers that make our job easier:

@SuppressWarnings(“unchecked”)
public final class Predicates {
  
  private Predicates() {
    throw new AssertionError(“Cannot be instantiated”);
  }
  public static <T> Predicate<T> asOneAnd(
      Predicate<T>… predicates) {
    Predicate<T> theOneAnd = Stream.of(predicates)
      .reduce(p -> true, Predicate::and);
      
    return theOneAnd;
  }
  
  public static <T> Predicate<T> asOneOr(
      Predicate<T>… predicates) {
    Predicate<T> theOneOr = Stream.of(predicates)
      .reduce(p -> false, Predicate::or);
      
    return theOneOr;
  }
}

So, the goal of these helpers is to take several predicates and glue them into a single composite predicate via the short-circuiting logical AND and OR.Let’s assume that we want to express a filter that applies the following three predicates via the short-circuiting logical AND:

Predicate<Car> pLexus=car -> car.getBrand().equals(“Lexus”);
Predicate<Car> pDiesel=car -> car.getFuel().equals(“diesel”);      
Predicate<Car> p250=car -> car.getHorsepower() > 250;    

First, we join these predicates in a single one:

Predicate<Car> predicateAnd = Predicates
  .asOneAnd(pLexus, pDiesel, p250);

Afterward, we express the filter:

List<Car> lexusDiesel250And = cars.stream()              
  .filter(predicateAnd)              
  .collect(Collectors.toList());

How about expressing a filter that produces a stream containing all cars having horsepower between 100 and 200 or 300 and 400? The predicates are:

Predicate<Car> p100 = car -> car.getHorsepower() >= 100;
Predicate<Car> p200 = car -> car.getHorsepower() <= 200;
      
Predicate<Car> p300 = car -> car.getHorsepower() >= 300;
Predicate<Car> p400 = car -> car.getHorsepower() <= 400;

The composite predicate can be obtained as follows:

Predicate<Car> pCombo = Predicates.asOneOr(
  Predicates.asOneAnd(p100, p200),
  Predicates.asOneAnd(p300, p400)
);

Expressing the filter is straightforward:

List<Car> comboAndOr = cars.stream()              
  .filter(pCombo)              
  .collect(Collectors.toList());

You can find all these examples in the bundled code.