Combining Predicate Types

The Java Predicate class provides default methods for combining predicates with logical and and logical or, but they don’t always work as expected.

Unexpected Behavior When Combining Predicates

The java.util.function.Predicate functional interface provides two methods to combine one predicate with another:

  • Predicate<T> and(Predicate<? super T> other) for logical and
  • Predicate<T> or(Predicate<? super T> other) for logical or

where <T> is the type of the predicate parameter.

Assuming that we want to write a predicate which checks whether a String is null or empty based on the following two predicates

Predicate<Object> isNull = o -> o != null;
Predicate<String> isEmpty = s -> s.isEmpty();  // assumes s != null

we’d like to define our new predicate

Predicate<String> isNullOrEmpty = isNull.or(isEmpty);  // won't compile

As the comment indicates this simple solution will not compile. The reason is that the generic parameter of isEmpty is not within the required bounds. Allowed are only parameter types which are a super class of Object (or Object itself), and String isn’t.

I have my own predicate class from times before Java 8, and I didn’t like this. You can easily write

String s = ...;
if (s == null || s.isEmpty()) ...

so this should work with predicates, too.

Obviously in this case I could just make isNull a Predicate<String> and everything would work nicely, but in general this is not always possible.

Solutions

Dedicated Methods

My first solution was to write new methods and_() and or_() (note the underscore) to allow for predicate arguments which were extending type predicate’s type <T>. As both arguments are Predicates it’s not possible to overload.

Here’s what the normal and() and the underscore and_() look like in my Predicate1<P> class:

  @NotNull
  default Predicate1<P> and(@NotNull Predicate<? super P> other)
  {
    return p -> test(p) && other.test(p);
  }


  @NotNull
  default <T extends P> Predicate1<T> and_(@NotNull Predicate<T> other)
  {
    return p -> test(p) && other.test(p);
  }

You see they look pretty the same, the code is exactly the same, only the parameter and the return type slightly differ.

It works, but it is awkward that the user is forced to use the correct method depending on the parameter types.

The real problem with this solution arises when we come to BiPredicate (a predicate with 2 parameters) where there are 4 possible parameter combinations. In my lib the BiPredicate substitute is Predicate2, and having predicates with up to 9 parameters would require to choose the correct one of 128 possible slightly differently named and() method depending on the parameter types for my Predicate9.

Static Methods

The main problem is the asymmetry of the situation when using methods. The resulting parameter type either depends on the basic object (this) or the other parameter. Basically the compiler should be able to determine the correct outcome, but this would only be possible with a static method where both objects are handled on the same level.

But how to declare such method. My first try was to define the dependencies in the generic parameters of the method, but both obvious versions wouldn’t compile:

  // WON'T COMPILE
  @NotNull
  static <T, T1 super T, T2 super T> Predicate<T> and(@NotNull Predicate<T1> pred1,
                                                      @NotNull Predicate<T2> pred2)
  {   
    p -> pred1.test(p) && pred2.test(p)
  }
                  
  // WON'T COMPILE
  @NotNull
  static <T1, T2, T extends T1 & T2> Predicate<T> and(@NotNull Predicate<T1> pred1,
                                                      @NotNull Predicate<T2> pred2)
  {   
    p -> pred1.test(p) && pred2.test(p)
  }

The first one does not compile because super isn’t allowed in the generic parameter list of a method, although I’m not sure why. Basically the compiler should be able to figure out the correct classes in this case, so maybe this is a Java 8 problem. But I’m stuck with Java 8 because of various customers using it, so I haven’t checked if newer implementations are more generous.

The second one has the problem that multiple extensions (i.e. T extends T1 & T2) requite the second parameter T2 to be an interface. As there is no guarantee that this will be the case (indeed in our example it isn’t) the compiler will reject it.

So the solution I came up with was a variant of the first one:

  @NotNull
  static <T> Predicate1<T> and(@NotNull Predicate<? super T> pred1,
                               @NotNull Predicate<? super T> pred2)
  {
    return p -> pred1.test(p) && pred2.test(p);
  }
 

As far as I can tell this gives the compiler the same information as the first try, but in this case it accepts it. And it is easily adapted to accept more generic parameters for Predicate2 (basically the same as BiPredicate) and its friends.

Usage is not as nice as with the method (example uses isNull and isEmpty defined above):

Predicate<String> isNullOrEmpty = Predicate1.or(isNull, isEmpty);

For me this is harder to read, but it’s still much nicer to handle and remember than having to use differently named methods, especially in cases with more generic parameters.

The compiler will not accept the method if the two types are not depending on each other so that either the first extends the second or vice versa. Eg you cannot or a Predicate<String> and a Predicate<java.awt.Point>.

Last Words

The de.caff.generics.function.Predicate1 , de.caff.generics.function.Predicate2 , …, de.caff.generics.function.Predicate9 , classes are part of the generics module of the de·caff Commons which can be downloaded for free from this website.