Introduction

In java terms, Predicate is an interface or more precisely, a Functional Interface added in java 8.
In simple terms, a predicate is an expression that tests for a certain condition and returns boolean true or false depending on whether the condition matches or not.

Example, checking if a string equals certain characters or a number matches some value.

In this article, we will be looking at Predicate usage, representing it as a Lambda expression, using Predicate in java streams, chaining predicates and much more.

Predicate interface
Predicate interface belongs to java.util.function package. It is a Functional Interface with a single method test() which takes one argument and returns a boolean value.
Thus, any expression which returns a boolean can be represented as a Predicate.
Creating and applying Predicate
Predicate is an interface so you can not create its object using any method.
Since it is a functional interface, it can be represented as a Lambda expression which takes a single argument and returns a boolean as shown below.

Predicate<String> p = str -> str.equals(“abc”);

This predicate checks if a string value is equal to “abc” and returns true if it is, false otherwise.
str in the predicate need not to be declared before, it is just a variable local to Lambda expression and can be anything.

Now, how do you use this predicate.
By using its test() method as below

Predicate<String> p = str -> str.equals("abc");
String toBeTested = "def";
// test predicate with another string
boolean result = p.test(toBeTested);
if(result) {
   System.out.println("Strings matched");
} else {
   System.out.println("Strings do not match");
}

Now you have a reusable Predicate which compares a string with “abc”.

If you want to create a method that takes a string argument and compares it with “abc”, then using Predicate, you can write it as below.

public boolean match(String s) {
   Predicate<String> p = str -> str.equals("abc");
   return p.test(s);
}

Note that since we are matching string values, the type of Predicate is a String. If we want to match integer values, then change the type of Predicate to Integer or use an IntPredicate.
Below illustration also shows the control flow of test().
predicate interface
Predicates in stream
Predicates are used in java stream api to filter out stream elements based on a condition.
filter() method of Stream takes an argument of type Predicate since it needs to select elements based on some condition.
That is what Predicate is designed for, to evaluate a condition.

Below is the signature of filter() method in java.util.stream.Stream interface.

Stream<T> filter(Predicate<? super T> predicate);

From the method signature, it is evident that the type of Predicate should be the same as the type of stream.

Suppose you have a list of Student objects and you want to get only those students whose age is greater than 15 years.
Following stream will use Predicate for this as below.

//create a predicate as per the condition
Predicate<Student> p = p -> p.getAge() > 15;
// filter records
List<Student> studentsGreaterThan15 = students.
                                      filter(p).
                                      collect(Collectors.toList());

Above code can more precisely be written as

List<Student> studentsGreaterThan15 = students.
                                      filter(st -> st.getAge() > 15).
                                      collect(Collectors.toList());

Chain Predicates
Till now, we looked at Predicate examples which match a single condition but what if we want to match multiple conditions such as we do in if-else or switch statements.

Predicates can be chained when there are multiple conditions need to be checked using its and() and or() methods which behave in the same way as logical AND(&&) and OR(||) operators respectively.

Suppose you want to check if a number lies between 10 and 20. A Predicate example for this condition can be written as below.

// create a greater than predicate
Predicate<Integer> greaterThanPredicate = num -> num > 10;
// create a lesser than predicate
Predicate<Integer> lesserThanPredicate = num -> num < 20;
// create a range predicate
Predicate<Integer> range = greaterThanPredicate.and(lesserThanPredicate);
int numberToCheck = 50;
// check if number lies between range
boolean result = range.test(numberToCheck); // will be false
if (result) {
    System.out.println(numberToCheck + " lies between 10 and 20");
} else {
   System.out.println(numberToCheck + " does not lie between 10 and 20");
}

Notice the use of and() method to join or chain predicates. You can join any number of predicates this way.
Above code can be shortened to

// create a greater than predicate
Predicate<Integer> greaterThanPredicate = num -> num > 10;
int result = greaterThanPredicate.and(num -> num < 20).test(50);

Similarly, if you want to check if a color is red or green, following Predicate can be written.

Predicate<String> red = c -> c.equals("red");
Predicate<String> green = c -> c.equals("red");
Predicate<String> color = red.or(green);
boolean result = color.test("yellow"); // returns false
boolean result = color.test("red"); // returns true

When test() is invoked on predicates joined with or(), it will return true if any of the predicates returns true.
Negate Predicate
A Predicate condition can be reversed using its negate() method. This is similar to applying a NOT(!) operator before a condition.
Suppose you want to check if a string does not contain certain characters, this can be written using a Predicate with negate() method as

// create a predicate
Predicate<String> containsCheck = str -> str.contains("valid");
// reverse it
Predicate<String> notContainsCheck = containsCheck.negate();
// check if string does NOT contain "valid"
notContainsCheck.test("This is invalid"); // returns true

This example can be shortened as

// create a predicate
Predicate<String> containsCheck = str -> str.contains("valid");
// check if string does NOT contain "valid"
containsCheck.negate().test("This is invalid"); // returns true

and(), or() and negate() methods return another Predicate, that is why, it is possible to chain them.

Note that Predicate is a Functional Interface having only one abstract method test().
All other methods such as and(). or(), negate() are default interface methods which are defined in the interface.

Hope this article was useful in learning the concept of Predicate interface introduced in java 8.

Leave a Reply