Java Predicates

In mathematics, a predicate is commonly understood to be a boolean-valued function 'P:X ? {true, false}', called the predicate on X. Let’s learn how Java Predicate interface helps in writing filter expressions so easy.

1. Java Predicate Interface

1.1. When to use Predicates

Introduced in Java 8, Predicate is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

So, where do you think, we can use these true/false returning functions in day-to-day programming? I will say we can use predicates anywhere we need to evaluate a condition on a collection of objects such that evaluation can result either in true or false.

For example, we can use predicates in these real-life usecases:

  • Find all children born after a particular date
  • Pizzas ordered within a specific time range
  • Employees older than certain age
  • and so on…

1.2. Using Predicate with Stream

As we know, Predicate is a functional interface that means we can pass it in lambda expressions wherever a predicate is expected. For example, one such method is filter() method from Stream interface.

/**
 * Returns a stream consisting of the elements of this stream that match
 * the given predicate.
 *
 * <p>This is an <a href="package-summary.html#StreamOps">intermediate
 * operation</a>.
 *
 * @param predicate a non-interfering stateless predicate to apply to each element to determine if it
 * should be included in the new returned stream.
 * @return the new stream
 */
Stream<T> filter(Predicate<? super T> predicate);

We can assume a Stream as a mechanism to create a sequence of elements supporting sequential and parallel aggregate operations. It means we can anytime collect and perform some operation of all elements present in the stream in one call.

So, essentially we can use stream and predicate to –

  • first filter certain elements from a group, and
  • then perform some operation on filtered elements.

2. Predicate Examples

To demonstrate the uses of Predicate with Java Stream, we have an Employee class as below:

public class Employee {

   private Integer id;
   private Integer age;
   private String gender;
   private String firstName;
   private String lastName;
   
   //Getters and setters are hidden for brevity
}

2.1. Creating Predicates

As mentioned earlier, predicates evaluate an expression and return a boolean value. Now let us see a few examples.

  • Predicate to find all employees who are male and age more than 21
public static Predicate<Employee> isAdultMale()
{
    return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
}
  • Predicate to find all employees who are female and age more than 18
public static Predicate<Employee> isAdultFemale()
{
    return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
}
  • Predicate to find all employees whose age is more than a given age
public static Predicate<Employee> isAgeMoreThan(Integer age)
{
    return p -> p.getAge() > age;
}

We can build more of them as and when needed.

2.2. Filtering Streams with Predicate

Let’s use the predicates created above. When we pass the predicate in the Stream.filter() method, it returns a new stream with matching items only, that we can collect in a new List.

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
 
public class EmployeePredicates
{
    public static Predicate<Employee> isAdultMale() {
        return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
    }
 
    public static Predicate<Employee> isAdultFemale() {
        return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
    }
 
    public static Predicate<Employee> isAgeMoreThan(Integer age) {
        return p -> p.getAge() > age;
    }
 
    public static List<Employee> filterEmployees (List<Employee> employees,
                                                Predicate<Employee> predicate)
    {
        return employees.stream()
                    .filter( predicate )
                    .collect(Collectors.<Employee>toList());
    }
}

In the above example, we have created another utility method filterEmployees() that basically makes code clean and less repetitive. We can also combine more than one predicate to make a predicate chain or complex predicate, as we do in builder pattern.

So, in this function, we pass the list of employees and we pass a predicate, then this function will return a new collection of employees satisfying the condition mentioned in parameter predicate.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static predicateExample.EmployeePredicates.*;
 
public class TestEmployeePredicates
{
    public static void main(String[] args)
    {
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");
 
        List<Employee> employees = new ArrayList<Employee>();
        employees.addAll(Arrays.asList(new Employee[]{e1, e2, e3, e4, e5, e6, e7, e8, e9, e10}));
 
        System.out.println( filterEmployees(employees, isAdultMale()) );
 
        System.out.println( filterEmployees(employees, isAdultFemale()) );
 
        System.out.println( filterEmployees(employees, isAgeMoreThan(35)) );
 
        //Employees less than or equals to 35
        //can be find using negate()
        System.out.println(filterEmployees(employees, isAgeMoreThan(35).negate()));
    }
}
[1 - 23, 3 - 43, 4 - 26, 8 - 79, 10 - 45]
[5 - 19, 7 - 68]
[3 - 43, 7 - 68, 8 - 79, 10 - 45]
[1 - 23, 2 - 13, 4 - 26, 5 - 19, 6 - 15, 9 - 15]

Predicates are really very good addition in Java 8 and I am going to use it whenever I will get the chance.

3. Conclusion

  • Predicates move the conditions (sometimes business logic) to a central place. This helps in unit-testing them separately.
  • Any code change need not be duplicated into multiple places thus predicates improve the code maintenance.
  • The names predicates such as “isAdultFemale()” are much more readable than writing a if-else block.

Happy Learning !!

Leave a Reply

22 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions, and frequently asked interview questions.

Our Blogs

REST API Tutorial