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 !!
is there any advantage to use predicate over legacy if-else ?
More readable code.
Great explaination. Thanks.
Great explanation !!!
Thanks a lot man.
Method Employee#toString is somewhat confusing when you run the code
Great! You really made this simple!
i never thought Learning about Predicate could be so easy. Thanks for a job well done.
Thanks for this!
Awesome.. Great explanation.. Easy to understand.. Keep it up!
Thanks, nice post
under TestEmployeePredicates shouldnt it be “EmployeesPredicates.filterEmployees()” and “EmployeesPredicates.isAdultFemale()” (e.g.)?
I have added static include : import static predicateExample.EmployeePredicates.*;
Thanks a lot, I didnt notice the static keyword. 🙂
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.
Hi,
In TestEmployeePredicates class,you called filterEmployee() method.where it is there and can you write a Predicate interface.
Filter method is defined in “EmployeePredicates”. It’s last method.
Yes, you can make predicate interface. Make all methods inside it, default methods.