Java 8 Predicate Example

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 !!

Sourcecode on Github

Leave a Comment

  1. 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?

    Reply
    • 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 list = new ArrayList();
      for(int i = 1; i< 100000; i++){ list.add(i); } long startTime = System.currentTimeMillis(); Stream
      stream = list.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

      Reply
      • This means using streams, slows down the performance atleast in this case. I tried this too, and with traditional approach results are faster.

        Reply
        • 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.

          Reply
          • 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.

Leave a Comment

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode