Predicate Chaining in Java

Introduction

We have been using the Predicate class in Java as part of Java streams filter method. Normally we would pass a predicate as a lambda expression to filter the stream elements based on that. In this post, we will look at the methods the Predicate class offers which allow us to do Predicate chaining in Java 8.

Using multiple predicates

Usually when we have more than one condition, we will use && or || as part of the Stream’s filter.

Multiple conditions

Say we have a list of integers and want to filter the integers divisible by both 4 and 5 (AND condition). We can do it in two ways:

list.stream()
    .filter(e -> e % 4 == 0)
    .filter(e -> e % 5 == 0)
    ...

We used two filter operations on the chain. First, filters (allow) only the numbers divisible by four. The second filters elements divisible by five.

Another way is to combine these into one condition as shown below.

list.stream()
    .filter(e -> e % 4 == 0 && e -> e % 5 == 0)
    ...

Either condition

For OR condition (divisible by either 4 or 5), we have to use the second style (single predicate with logical OR operation).

list.stream()
    .filter(e -> e % 4 == 0|| e -> e % 5 == 0)
    ...

Problem with multiple predicate conditions

The main problem with the above approach is readability. The lambda expression, when it has AND or OR conditions, becomes difficult to read.

We could improve it by moving the complex condition to a private method, but still we have pushed the problem (readability) into the new private method.

list.stream()
    .filter(this::isDivisibleByFourOrFive)
    ...
    
private boolean isDivisibleByFourOrFive(int e) {
    return e % 4 == 0 || e % 5 == 0;
}

Since it is not readable, when we have a complex condition, it is prone to errors (when we make a change to them).

The Predicate class has method using which we can do predicate chaining in Java. This will make it more readable  (will appear as we speak) and leads to fewer errors.

Predicate

A Predicate is a functional interface that represents a boolean-valued function of one argument. It has a single abstract method (hence a functional interface) named test, which accepts an argument and returns a boolean.

Predicate chaining methods

The Predicate class has two default methods using which we can compose predicates – and and or.

Predicate.and

This method returns a composedpredicate that represents a short-circuiting logical ANDoperation of this predicate and another (in that order). In other words, it evaluates the first predicate (the predicate object on which the and method is called) first. 

  • If it results in true, then the other predicate (composed predicate) is evaluated.
  • If the first predicate is false, the other predicate is not evaluated.

Predicate.or

The or method will return a composed predicate that represents a short-circuiting logical OR operation of this predicate and another. It will evaluate the first predicate (the predicate object on which the or method is called), followed by the second.

If the evaluation of the first predicate is true, the second will not be evaluated.

Predicate.negate and Predicate.not

There is a method called negate which returns a new predicate that is the logical NOT or negation of the passed predicate.

Since Java 11, there is a static method not which achieves the same purpose.

Predicate examples

Let us create a list of integers and define two predicates divisibleByFour and divisibleByFive.

List<Integer> ints = List.of(10, 20, 23, 24, 25, 27);
Predicate<Integer> divisibleByFour = n -> n % 4 == 0;
Predicate<Integer> divisibleByFive = n -> n % 5 == 0;

We stream the integer, apply the predicate (filter by that) and collect the result as a list.

System.out.println(ints.stream()
        .filter(divisibleByFour)
        .collect(Collectors.toList()));

This outputs,

[20, 24]

Similarly for the other predicate,

System.out.println(ints.stream()
        .filter(divisibleByFive)
        .collect(Collectors.toList())); 

Outputs,

[10, 20, 25]

Predicate and

Let us create a composite predicate by logical AND of the two predicates (divisible by four and five).

Call the and method on the divisibleByFour predicate and pass the divisibleByFive predicate to get a new composed predicate. The new predicate first evaluates divisibleByFour predicate, and if it is true, will evaluate the second (divisibleByFive) predicate.

Predicate<Integer> divisibleByFourAndFive = divisibleByFour.and(divisibleByFive);
System.out.println(ints.stream()
        .filter(divisibleByFourAndFive)
        .collect(Collectors.toList())); //[20]

Predicate or

Similarly, let us use the or method to form a composed predicate – {divisible by four} OR {divisible by five}
It will not evaluate the second predicate if the first is true (since we are doing a logical OR).

Predicate<Integer> divisibleByFourOrFive = divisibleByFour.or(divisibleByFive);
System.out.println(ints.stream()
        .filter(divisibleByFourOrFive)
        .collect(Collectors.toList())); //[10, 20, 24, 25]

Predicate negate

Negating a predicate adds a NOT to the condition. Thus calling the negate method on divisibleByFour returns a predicate that filters only elements not divisible by four.
Since Java 11, we can use the not static method as well.

Predicate<Integer> notDivisibleByFour = divisibleByFour.negate();
System.out.println(ints.stream()
        .filter(notDivisibleByFour)
        .collect(Collectors.toList())); //[10, 23, 25, 27]

//not (since 11) same as negate
notDivisibleByFour = Predicate.not(divisibleByFour);
System.out.println(ints.stream()
        .filter(notDivisibleByFour)
        .collect(Collectors.toList())); //[10, 23, 25, 27]

Predicate chaining – No precedence

The normal Java’s operator precedence are not applicable in the chained predicate evaluation. In normal expression evaluation, when we have an expression of the form `A || B && C`, the expression `B && C` will be evaluated first. Then `A || <result>` will be evaluated where <result> is the result of `B && C`.
But, if we compose a predicate of the form `A || B && C`, it will be evaluated from left to right (irrespective of the operator precedence). If A is true, it will not evaluate the other predicates.

Example: We construct the below complex predicate:

{divisible by four} OR {is number even} AND {divisible by five}
(A || B && C)
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> complexPredicate = divisibleByFour.or(isEven.and(divisibleByFive));
System.out.println(ints.stream()
        .filter(complexPredicate)
        .collect(Collectors.toList())); //[10, 20, 24]

Let us verify that B && C was not evaluated first. First, let us add a print statement for the second predicate (isEven). Then, for those numbers that are divisible by four (first predicate returns true), the other predicates are not evaluated because the first part of the OR predicate has resulted in a success (short-circuits).

// B is not evaluated when A is true.
//for 20 and 24, it does not print anything
Predicate<Integer> isEven = n -> {
    System.out.println("Evaluating even for " + n);
    return n % 2 == 0;
};
Predicate<Integer> complexPredicate = divisibleByFour.or(isEven.and(divisibleByFive));
System.out.println(ints.stream()
        .filter(complexPredicate)
        .collect(Collectors.toList())); //[10, 20, 24]

Prints,

Evaluating even for 10
Evaluating even for 23
Evaluating even for 25
Evaluating even for 27
[10, 20, 24]

Conclusion

This concludes the post on Predicate chaining or composition in Java 8. We learnt how to compose multiple predicates using and, or, and negate (not) methods in Predicate class.

Leave a Reply