In BiPredicate Part 1 , we learnt about using test and negate functions. In this article we will use default methods and and or methods to compose BiPredicates, which can perform all the tests of composing BiPredicates.

Our use case is to create a system, which helps HR personnel to identify potential candidates for hiring.

We will check for three test conditions.

  1. Age of the software engineer -> Because Ageism is a real thing in the software industry
  2. Salary of Software Engineer -> Because money matters… Lower the salary – it is better for HR 🙂
  3. Distance from the office -> Employees who tend to live near office tend to stay with the company for longer time.

This is our person class. It has field for age, salary, distanceFromOffice and name (Because, an employee should have a name).

static class Person {
    int age;
    int salary;
    int distanceFromOffice;
    String name;

    public Person(int age, int salary, int distanceFromOffice, String name) {
        this.age = age;
        this.salary = salary;
        this.name = name;
        this.distanceFromOffice = distanceFromOffice;
    }
}

Now, We have to create a list of 5 potential candidates.

List<Person> listOfCandidates = List.of( new Person(30, 200000, 21, "Name1"),
                                         new Person(42, 41000, 1, "Name2"), 
                                         new Person(22, 21000, 9, "Name3"),
                                         new Person(21, 31000, 15, "Name4"), 
                                         new Person(21, 210000, 10, "Name5"));

So, we have 5 candidates now.

Let us create BiPredicates to test for individual conditions.

Condition 1 :- 

To check, if a candidate is older than the other candidate.

BiPredicate<Person, Person> older = (candidate1, candidate2) -> candidate1.age > candidate2.age;

Condition 2 :-

To Check, if a candidate is asking for lesser salary or cheaper than the other candidate.

BiPredicate<Person, Person> cheaper = (candidate1, candidate2) -> candidate1.salary < candidate2.salary;

Condition 3 :-

To check,  which candidates lives nearer to office.

BiPredicate<Person, Person> livesNearTheOffice = (candidate1, candidate2) -> candidate1.distanceFromOffice < candidate2.distanceFromOffice;

Composing new BiPredicate 1 :- To check for all the above conditions in one test.

We will use the and function to chain multiple BiPredicates to create a new one.

BiPredicate<Person, Person> isOlderAndCheaperAndLivesNearTheOffice = older
                                                                          .and(cheaper)
                                                                          .and(livesNearTheOffice);

The above predicate will return true for the candidate, if they satisfy all of the above conditions, i.e he is older, he is asking for less salary and he lives near the office.

Composing new BiPredicate 2 :- To check for condition, where  the candidate is either cheaper or older.

For this composition, we will use or default method to create a short circuiting of two tests – old and cheap.

BiPredicate<Person, Person> olderOrCheaper = older
                                                  .or(cheaper);

This BiPredicate will return true for if any one of the cases are true.

We can use and and or to create many different combinations for tests.

This can be easily used to replace && and || in if-else statements.

By using BiPredicate and it’s short-circuiting operators, we can reuse test-conditions in multiple places in our codes. And all of these tests can be put in one different java file.

Then it can be accessed directly, if they are made static.

This is the complete code.

import java.util.function.BiPredicate;
import java.util.List;
import static java.lang.System.out;


public class BiPredicate_AndOr{
    public static  void main(String [] args) {
        List<Person> listOfCandidates = List.of( new Person(30, 200000, 21, "Name1"),new Person(42, 41000, 1, "Name2"), new Person(22, 21000, 9, "Name3"),
                                              new Person(21, 31000, 15, "Name4"), new Person(21, 210000, 10, "Name5"));
        BiPredicate<Person, Person> older = (candidate1, candidate2) -> candidate1.age > candidate2.age;
        BiPredicate<Person, Person> cheaper = (candidate1, candidate2) -> candidate1.salary < candidate2.salary;
        BiPredicate<Person, Person> livesNearTheOffice = (candidate1, candidate2) -> candidate1.distanceFromOffice < candidate2.distanceFromOffice;

        BiPredicate<Person, Person> isOlderAndCheaperAndLivesNearTheOffice = older.and(cheaper).and(livesNearTheOffice);
        Person finalCandidate = reduce(listOfCandidates, isOlderAndCheaperAndLivesNearTheOffice);
        out.println("Candidate, who is Oldest, cheapest and nearest to office, He is Mr. Name :- " + finalCandidate.name);

        //Someone who is either older [because he will have more experience] or cheaper [because He will cost less to the company]
        BiPredicate<Person, Person> olderOrCheaper = older.or(cheaper);
        finalCandidate = reduce(listOfCandidates, olderOrCheaper);
        out.println("Candiate who is either older or cheaper. He is Mr. " + finalCandidate.name);

    }

    //It will reduce the list-of-person to single person
    private static Person reduce(List<Person> listOfPerson, BiPredicate<Person, Person> selectionCriteria) {
        Person selectedCandidate = null;
        for(Person newCandidate : listOfPerson) {
            if(selectedCandidate == null) {
                selectedCandidate = newCandidate;
            } else {
                //If reduction condition tests to true - update the person
                if(selectionCriteria.test(newCandidate, selectedCandidate) == true) {
                    selectedCandidate = newCandidate;
                }
            }
        }
        return selectedCandidate;
    }

    static class Person {
        int age;
        int salary;
        int distanceFromOffice;
        String name;

        public Person(int age, int salary, int distanceFromOffice, String name) {
            this.age = age;
            this.salary = salary;
            this.name = name;
            this.distanceFromOffice = distanceFromOffice;

        }
    }

}

Output :-

Candidate, who is Oldest, cheapest and nearest to office, He is Mr. Name :- Name2
Candiate who is either older or cheaper. He is Mr. Name3