Using BiPredicate – Functional style programming – extending API

186 Using BiPredicate

Let’s consider the Car model and a List<Car> denoted as cars:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  …
}

Our goal is to see if the following Car is contained in cars:

Car car = new Car(“Ford”, “electric”, 80);

We know that the List API exposes a method named contains(Object o). This method returns true if the given Object is present in the given List. So, we can easily write a Predicate as follows:

Predicate<Car> predicate = cars::contains;

Next, we call the test() method and we should get the expected result:

System.out.println(predicate.test(car)); // true

We can obtain the same result in a stream pipeline via filter(), anyMatch(), and so on. Here is via anyMatch():

System.out.println(
  cars.stream().anyMatch(p -> p.equals(car))
);

Alternatively, we can rely on BiPredicate. This is a functional interface representing a two-arity specialization of the well-known Predicate. Its test(Object o1, Object o2) method gets two arguments, so it is a perfect fit for our case:

BiPredicate<List<Car>, Car> biPredicate = List::contains;

And, we can perform the test as follows:

System.out.println(biPredicate.test(cars, car)); // true

In the next problem, you’ll see a more practical example of using a BiPredicate.

187. Building a dynamic predicate for a custom model

Let’s consider the Car model and a List<Car> denoted as cars:

public class Car {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  …
}

And, let’s assume that we need to dynamically produce a wide range of predicates that applies the operators <, >, <=, >=,!=, and == to the horsepower field. It will be cumbersome to hardcode such predicates, so we have to come up with a solution that can build on the fly any predicate that involves this field and one of the comparison operators listed here.There are a few approaches to accomplish this goal, and one of them is to use a Java enum. We have a fixed list of operators that can be coded as enum elements as follows:

enum PredicateBuilder {
  GT((t, u) -> t > u),
  LT((t, u) -> t < u),
  GE((t, u) -> t >= u),
  LE((t, u) -> t <= u),
  EQ((t, u) -> t.intValue() == u.intValue()),
  NOT_EQ((t, u) -> t.intValue() != u.intValue());
  …

In order to apply any of these (t, u) lambdas, we need a BiPredicate constructor (see Problem 186) as follows:

  private final BiPredicate<Integer, Integer> predicate;
  private PredicateBuilder(
      BiPredicate<Integer, Integer> predicate) {
    this.predicate = predicate;
  }
  …

Now that we can define a BiPredicate, we can write the method that contains the actual test and returns a Predicate<T>:

  public <T> Predicate<T> toPredicate(
      Function<T, Integer> getter, int u) {
    return obj -> this.predicate.test(getter.apply(obj), u);
  }
  …

Finally, we have to provide here the Function<T, Integer> which is the getter corresponding to horsepower. We can do this via Java Reflection as follows:

public static <T> Function<T, Integer> getFieldByName(
    Class<T> cls, String field) {
  return object -> {
    try {
      Field f = cls.getDeclaredField(field);
      f.setAccessible(true);
      return (Integer) f.get(object);
    } catch (IllegalAccessException | IllegalArgumentException
           | NoSuchFieldException | SecurityException e) {              
      throw new RuntimeException(e);
    }
  };
}

Of course, it can be any other class and integer field as well, not only the Car class and the horsepower field. Based on this code, we can dynamically create a predicate as follows:

Predicate<Car> gtPredicate
  = PredicateBuilder.GT.toPredicate(
      PredicateBuilder.getFieldByName(
        Car.class, “horsepower”), 300);

Using this predicate is straightforward:

cars.stream()
    .filter(gtPredicate)
    .forEach(System.out::println);

You can use this problem as an inspiration point for implementing more types of dynamic predicates. For example, in the next problem, we use the same logic in another scenario.