Logging in predicates – Functional style programming – extending API

189. Logging in predicates

We already know that the Predicate functional interface relies on its test() method to perform the given check and it returns a boolean value. Let’s suppose that we want to alter the test() method to log the failure cases (the cases that leads to the return of a false value).A quick approach is to write a helper method that sneaks the logging part as follows:

public final class Predicates {
  private static final Logger logger
    = LoggerFactory.getLogger(LogPredicate.class);
  private Predicates() {
    throw new AssertionError(“Cannot be instantiated”);
  }
  public static <T> Predicate<T> testAndLog(
      Predicate<? super T> predicate, String val) {
    return t -> {
      boolean result = predicate.test(t);
      if (!result) {
        logger.warn(predicate + ” don’t match ‘” + val + “‘”);
      }
      return result;
    };
  }
}

Another approach consists of extending the Predicate interface and providing a default method for testing and logging the failure cases as follows:

@FunctionalInterface
public interface LogPredicate<T> extends Predicate<T> {
  Logger logger = LoggerFactory.getLogger(LogPredicate.class);
 
  default boolean testAndLog(T t, String val) {
    boolean result = this.test(t);
    if (!result) {
      logger.warn(t + ” don’t match ‘” + val + “‘”);
    }
    return result;
  }
}

You can practice these examples in the bundled code.

190. Extending Stream with containsAll, containsAny

Let’s assume that we have the following code:

List<Car> cars = Arrays.asList(
  new Car(“Dacia”, “diesel”, 100),
  new Car(“Lexus”, “gasoline”, 300),
  …
  new Car(“Ford”, “electric”, 200)
);
     
Car car1 = new Car(“Lexus”, “diesel”, 300);
Car car2 = new Car(“Ford”, “electric”, 80);
Car car3 = new Car(“Chevrolet”, “electric”, 150);
List<Car> cars123 = List.of(car1, car2, car3);

Next, in the context of a stream pipeline, we want to check if cars contain all/any of the car1, car2, car3, or cars123.The Stream API comes with a rich set of intermediate and final operations but it doesn’t has a built-in containsAll()/containsAny(). So, it is our mission to provide the following final operations:

boolean contains(T item);
boolean containsAll(T… items);
boolean containsAll(List<? extends T> items);
boolean containsAll(Stream<? extends T> items);
boolean containsAny(T… items);
boolean containsAny(List<? extends T> items);
boolean containsAny(Stream<? extends T> items);

We highlighted the methods that get a Stream argument since these methods provide the main logic while the rest of the methods are just calling these ones after converting their arguments to a Stream.

Exposing constainsAll/Any() via a custom interface

The containsAll(Stream<? extends T> items) relies on a Set to accomplish its job as follows (you can challenge yourself to find an alternative implementation):

default boolean containsAll(Stream<? extends T> items) {
  Set<? extends T> set = toSet(items);
  if (set.isEmpty()) {
    return true;
  }
  return stream().filter(item -> set.remove(item))
                 .anyMatch(any -> set.isEmpty());
}

The containsAny(Stream<? extends T> items) relies also on a Set:

default boolean containsAny(Stream<? extends T> items) {
  Set<? extends T> set = toSet(items);
  if (set.isEmpty()) {
    return false;
  }
  return stream().anyMatch(set::contains);
}

The toSet() method is just a helper that collects the Stream items into a Set:

static <T> Set<T> toSet(Stream<? extends T> stream) {
  return stream.collect(Collectors.toSet());
}

Next, let’s sneak these codes into their final place which is a custom interface.As you can see, the containsAll(Stream<? extends T> items) and containsAny(Stream<? extends T> items) are declared as default which means that they are part of an interface. Moreover, both of them call the stream() method that is also part of this interface and hooks the regular Stream.Basically, a quick approach for solving this problem (especially useful in interviews) consists of writing this custom interface (let’s arbitrarily name it Streams) that has access to the original built-in Stream interface as follows:

@SuppressWarnings(“unchecked”)
public interface Streams<T> {
  Stream<T> stream();
  static <T> Streams<T> from(Stream<T> stream) {
    return () -> stream;
  }
  …

Next, the interface exposes a set of default methods that represent the containsAll()/containsAny() flavors as follows:

  default boolean contains(T item) {
    return stream().anyMatch(isEqual(item));
  }
  default boolean containsAll(T… items) {
    return containsAll(Stream.of(items));
  }
  default boolean containsAll(List<? extends T> items) {
    return containsAll(items.stream());
  }
  default boolean containsAll(Stream<? extends T> items) {
    … 
  }
  default boolean containsAny(T… items) {
    return containsAny(Stream.of(items));
  }
  default boolean containsAny(List<? extends T> items) {
    return containsAny(items.stream());
  }
  default boolean containsAny(Stream<? extends T> items) {
    …
  }
  static <T> Set<T> toSet(Stream<? extends T> stream) {
    …
  }
}

Done! Now, we can write different stream pipelines that use the brand new containsAll/Any() operations. For instance, if we want to check if cars contain all items from cars123, we express the stream pipeline as follows:

boolean result = Streams.from(cars.stream())
  .containsAll(cars123);

Here are several more examples:

boolean result = Streams.from(cars.stream())
  .containsAll(car1, car2, car3);
boolean result = Streams.from(cars.stream())
  .containsAny(car1, car2, car3);

Involving more operations can be done as in the following example:

Car car4 = new Car(“Mercedes”, “electric”, 200);      
boolean result = Streams.from(cars.stream()
    .filter(car->car.getBrand().equals(“Mercedes”))
    .distinct()
    .dropWhile(car -> car.getFuel().equals(“gasoline”))
  ).contains(car4);

A more expressive and complete solution to this problem will consist of extending the Stream interface. Let’s do it!