Java provides a new interface named Predicate which is a functional interface.
A functional interface is an interface which will have exactly one abstract method in addition to one or more default methods.
In the case of Predicate interface, the abstract method is test method whose signature is as shown below
boolean test(T t)
In general a predicate is a condition which when evaluated will return either true or false.
This interface allows us to create predicates so that we can separate predicate logic from other business code and make it reusable.
The below code will give an example. The below code creates list of person objects and create two separate predicate. The two predicate can be passed to the filter method to filter the list.
Predicate 1
package Function;
import java.util.function.Predicate;
public class LNamePredicate implements Predicate {
private String lName;
@Override
public boolean test(Person person) {
if(lName.equals(person.getLname())) {
return true;
}
return false;
}
public void setlName(String lName) {
this.lName = lName;
}
}
Predicate 2
package Function;
import java.util.function.Predicate;
public class FNamePredicate implements Predicate {
private String fName;
@Override
public boolean test(Person person) {
if(fName.equals(person.getFname())) {
return true;
}
return false;
}
public void setfName(String fName) {
this.fName = fName;
}
}
Now the two predicates are used to filter the list of Person objects as shown in the main code
Main Code
package Function;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class PredicateDemo1 {
public static void main(String[] args) {
List list = new ArrayList(10);
for(int i = 0 ; i < 10; i++) {
Person person = new Person();
person.setSsn("ssn" + i);
person.setFname("fname" + i);
person.setLname("lname" + i);
person.setDescription("description" + i);
person.setAge(i);
list.add(person);
}
FNamePredicate fNamePredicate = new FNamePredicate();
fNamePredicate.setfName("fname5");
LNamePredicate lNamePredicate = new LNamePredicate();
lNamePredicate.setlName("lname5");
Predicate namePredicate = fNamePredicate.and(lNamePredicate);
List list1 = filter(fNamePredicate, list);
display("First name query", list1);
System.out.println();
list1 = filter(lNamePredicate, list);
display("Last name query", list1);
System.out.println();
list1 = filter(namePredicate, list);
display("Name and query", list1);
System.out.println();
fNamePredicate.setfName("fname1");
list1 = filter(fNamePredicate, list);
display("First name query", list1);
System.out.println();
lNamePredicate.setlName("lname2");
list1 = filter(lNamePredicate, list);
display("Last name query", list1);
System.out.println();
namePredicate = fNamePredicate.or(lNamePredicate);
list1 = filter(namePredicate, list);
display("Name or query", list1);
System.out.println();
Predicate fNamePredicate1 = fNamePredicate.negate();
list1 = filter(fNamePredicate1, list);
display("Negate of First name query", list1);
}
public static List filter(Predicate predicate, List list) {
List filteredList = new ArrayList();
for(Person person : list) {
if(predicate.test(person)) {
filteredList.add(person);
}
}
return filteredList;
}
public static void display(String queryName, List list) {
System.out.println(queryName);
for(Person person : list) {
System.out.println(person);
}
}
}
Output
Explanation
The advantage of using this approach is that we will be following one of the design principle which is
Separate what varies from what remains same and encapsulate them.
In above code filter business logic changes, sometimes we want to filter the list by first name and sometimes by last name. So we seperate them from what remains same, which is looping through each item and testing the predicate and if the predicate passes, add them to the new list otherwise skip the item.
We define a family of filter algorithms which are LNamePredicate, FNamePredicate, AgePredicate etc.
The client using these algorithms should able to interchange at runtime. The client here is main code. To make that happen we follow another design principle which is
Program to interface not to an implementation
Since all these algorithms implements Predicate interface we can easily change them at runtime without requiring to create another filter method.
All this leads us to Strategy Pattern which is
Create a family of algorithms by separating them from what remains same, encapsulate them, make them swappable without affecting the client code.