In mathematics, a predicate is commonly understood as a boolean-valued function 'P:X ? {true, false}'
, called the predicate on X. Let’s learn how the Java Predicate interface helps write filter expressions easily.
1. When to use a Predicate
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 a certain age
- and so on…
2. Creating a Predicate
As mentioned earlier, predicates evaluate an expression and return a boolean value. Now let us see a few examples of creating simple predicates with a simple example.
Predicate<Employee> isAdult = e -> e.getAge() > 18;
Predicate<Employee> isMale = p -> p.getGender().equalsIgnoreCase("M");
We can create a complex Predicate by mixing two or more predicates.
Predicate<Employee> isAdultMale= isAdult.and(isMale);
Predicate<Employee> isAdultOrMale = isAdult.or(isMale);
It is possible to create a reverse predicate of an existing Predicate using the negate() method.
Predicate<Employee> isMinor = isAdult.negate();
3. Using Predicate with Streams
As we know, Predicate is a functional interface, meaning 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.
*/
Stream<T> filter(Predicate<? super T> predicate);
So, essentially we can use stream and predicate to –
- first filter certain elements from a group, and
- then, perform some operations on filtered elements.
In the following example, we find all the male employees using the Predicate isMale and collect all male employees into a new List.
Predicate<Employee> isMale = p -> p.getGender().equalsIgnoreCase("M");
List<Employee> maleEmployeeList = employeeList.stream().filter(isMale).toList();
Note that if we want to use two arguments to test a condition, we can also use BiPredicate class.
BiPredicate<Integer, String> isAdultMale = (p1, p2) -> p1 > 18 && p2.equalsIgnoreCase("M");
List<Employee> adultMalesList = employeeList.stream()
.filter(x -> isAdultMale.test(x.getAge(), x.getGender()))
.toList();
4. 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 code maintenance.
- The names predicates such as “isAdultFemale()” are much more readable than writing a if-else block.
Happy Learning !!
Leave a Reply