The Predicate interface is part of Java 8 functional programming enhancements. A Predicate is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
Implementation-wise, a Predicate is used to pass the behavior (functions) as arguments to methods. In simple words, a Predicate is essentially a boolean-valued function that takes an input and returns true or false.
For reference, in mathematics as well, a predicate is commonly understood as a boolean-valued function 'P:X ? {true, false}'
, called the predicate on X.
// Returns true if number is even; else false.
Predicate<Integer> evenPredicate = n -> n % 2 == 0;
// Returns true if age is greater than or equals to 18; else false.
Predicate<Person> canVote = p -> p.age() >= 18;
//Stream filtering with Predicate
List<Person> voters = personList.stream().filter(canVote).toList();
1. When to use a Predicate
So, where do you think, we can use these true/false returning functions in day-to-day programming?
We can use predicates to filter objects from a Collection after evaluating a condition on the objects (in the Collection) such that evaluation can result either in true or false.
For example, we can use predicates in the following 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
2.1. Simple 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");
2.2. Complex Predicate
We can create a complex Predicate by mixing two or more predicates.
Predicate<Employee> isAdultMale= isAdult.and(isMale);
Predicate<Employee> isAdultOrMale = isAdult.or(isMale);
2.3. Reverse Predicate
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 Java 8 Stream
As we know, the 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 the 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.
4. Stream Filter Example with Predicate
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();
5. Conclusion
In conclusion, the Predicate
functional interface in Java provides a way to define and use boolean-valued conditions as first-class objects, making the application code more flexible and expressive.
- 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 of the predicates such as “isAdultFemale()” are much more readable than writing an if-else block.
Happy Learning !!
is there any advantage to use predicate over legacy if-else ?
More readable code.
Using predicates will have performance impact on large collection of Objects right… ???
I have tried filtering out the even numbers from the a list containing 0-1500 using Guava Predicates and normal conventional way of for and if loop implementation .
I guess it will apply to the Java Se8 Predicate case as well right?
I also tried to find the difference in performance. You are right it’s much bigger than alternate ways of for loop iterations. Below is code I tried and executed it multiple times with almost same result.
public static void main(String[] args){ list = new ArrayList (); stream = list.stream();
List
for(int i = 1; i< 100000; i++){ list.add(i); } long startTime = System.currentTimeMillis(); Stream
Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
System.out.println(evenNumbersArr);
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
startTime = System.currentTimeMillis();
Integer[] evenNumbersArr2 = new Integer[list.size()];
for(Integer i : list){
if(i%2 == 0) evenNumbersArr2[i] = i;
}
System.out.println(evenNumbersArr2);
endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
Output:
[Ljava.lang.Integer;@d46ca6
57
[Ljava.lang.Integer;@117d9a3
3
This means using streams, slows down the performance atleast in this case. I tried this too, and with traditional approach results are faster.
With a trivial example like this then yes the overhead of stream processing is probably not worth it. I think that one of the central tenets of streams is that you can chain operations together and they are ‘squashed’ and only evaluated once on the terminal operation, whereas pre-Java8 you would have to loop over the collection each time for each operation. The stream API also gives you the powerful option to parallelise the processing simply by calling .parallel() on an existing stream.
That’s true.
.parallel()
really is very powerful in handling large datasets.Streams are useful when they are used parallel for large collections otherwise its an overhead to create streams.
I also had evaluated the performance for loops and parallel streams win in this case.
For small collections traditional or Java 5 way is useful.
Agree. There is a trade off between clean code and performance gain. For small collections , the performance gain is not that much but the code readability increase many fold. I will go for clean code in this case considering powerful hardware available these days.