What is Predicate in Java 8?
Learn via video course
The Predicate in Java 8 is one of the types of Functional Interfaces. Functional Interfaces were introduced in Java 8 to support functional programming in the object-oriented world of Java. The Predicate is one of them, which allows programmers to test a specific condition imposed on values or objects. Such a condition will always return a boolean value.
To understand better what is Predicate interface, you must first understand what the word "predicate" mean. The term "predicate" implies a statement that evaluates whether a value is true or false.
The next thing that you need to understand is Functional Interface. Functional Interface is nothing but just an interface with a "single abstract method", also commonly termed as SAM. They are annotated with @FuctionalInterface annotation. These interfaces also act as a target type for lambda expressions.
Note: Abstract methods are common to interfaces and abstract classes. Such methods don't require implementation for their declaration. They are defined using the abstract keyword, but in interfaces, all methods are implicitly abstract if they are not declared as default or static. So it is optional to use the abstract keyword for interface methods.
Now, you can easily understand the Predicate interface as it combines the above two terms. Predicate in Java 8 is defined in the java.util.function package; it is an interface that contains only one abstract method. It will be easy to guess what that method would do and return. Since we are talking about predicates, this method would test a value or an object with a condition and returns a boolean value. The method is named test for this apparent reason and has a boolean return type.
It is defined in the specified package as:
Hence we need to override the test predicate method in Java 8 to utilize them. The below example shows how:
Explanation: You can create your class by implementing the Predicate interface and overriding the test method or create a more concise code by lamdba expressions. Notice the target assignment of the lambda expression is Predicate<Integer> as it runs a test condition on an integer value.
When to Use Predicates?
- Code reusability in unit-testing
- For tasks involving item evaluation based on predefined criteria.
- To apply filter for a collection of objects.
1. Code Reusability in Unit-Testing
By separating the conditions required for the checks, Predicates prove to be very helpful in unit-testing as they can be reused in different kinds of tests.
As you also saw in the above code, how Predicates combined with lambda expression result in a lot lesser code. So you can use Predicate in Java 8 to ensure that the code is easy to understand and manage because they have more readability than if-else checks.
2. Item Evaluation Based on Conditions
Wherever you feel there's a need to evaluate items (item could be a value or an object) that can result in true or false, you can use Predicates.
Scenarios like:
- Checking if the account balance is more than a certain amount for a specific type of account, say, a savings account.
- Checking if a person is eligible to vote.
and many more.
Practically, you can use Predicates wherever there are conditions to be checked.
3. Using Predicates as a Condition for Filtering for a Collection of Items
Let's say you have a bag full of fruits like Apples, Oranges, Mangoes, Guavas, etc. This bag can be considered a collection of fruits. Now, these fruits need to pass a test before you can eat them. An eatable fruit must be fresh and should weigh at least 75 grams. Such a test can be translated into Java language through Predicates. The Predicates stores this testing logic and applies it to a collection to filter it.
You can use Predicates in Java 8 to extract out specific values from a collection like:
- Extracting out odd numbers from a list of numbers.
- Employees that joined a company between certain dates.
and many other real life scenarios.
Java Predicate Interface Methods
There are handful of methods provided by the Predicate interface. These are:
Method | Description about parameter | Description about returned value |
---|---|---|
boolean test(T t) | t: Input argument | true if the input argument t matches the predicate |
default Predicate<T> and(Predicate<? super T> other | other: A predicate that will be logically-ANDed with this predicate | A composed predicate that represents a short-circuiting logical AND of this predicate and another |
default Predicate<T> or(Predicate<? super T> other) | other: A predicate that will be logically-ORed with this predicate | A composed predicate that represents the short-circuiting logical OR of this predicate and the other predicate |
default Predicate<T> negate() | No parameter required | A predicate that represents the logical negation of this predicate |
static <T> Predicate<T> isEqual(Object targetRef) | T: Type of arguments to predicate,targetRef: The object reference with which to compare for equality, which may be null | A predicate that tests if two arguments are equal according to Objects.equals(Object, Object) |
Java Predicate Interface Examples
There are several places where Predicates can be used. Some of them include:
1. A Simple Predicate in Java 8
Output:
Explanation: This code defines a Predicate on integer values that tests if their value is less than 20. Predicate<Integer> is used to create Predicates that operate on integers. The lambda expression num -> num < 20 must be compatible with the type of variable it is assigned to i.e, it's body , num < 20 in this case, must test a condition on an integer and return a boolean value.
2. Predicate Chaining with "and" Method
Sometimes conditions become complex when two or more conditions are to be checked simultaneously. In such a case, two or more Predicates can be chained together, and the resulting Predicate can be used to test a complex condition.
Output:
Explanation: The Predicate isAdult will store the check for ages more than or equal to 18. And, notASeniorCitizen Predicate stores the check for ages less than 60. The composed Predicate combinedPredicateForAge created through the and method stores a check that is logical AND of both conditions, i.e., ages more than or equal to 18 but less than 60.
3. Predicate Chaining with or Method
A similar Predicate chaining can also be done through the or method.
Output:
Explanation: The program creates two Predicates in Java 8 that operate on strings. startsWithA stores the check to test if they start with the letter 'A'. endsWithZ stores the check to test if they end with the letter 'Z'. The composed predicate generated with the or method will store the check for logical OR of both conditions, i.e., strings will only pass the test with startsWithAOrEndsWithZ (the composed Predicate) if either they start with the letter 'A' or end with the letter 'Z' or both.
4. Negating a Predicate by "negate" Method
This method is used on a Predicate to check for obtaining its negation i.e., a logically opposite Predicate.
Output:
Explanation: The Predicate line in above code is created to store the check for ages more than 18 but uses the negate method to reverse that check. Reversal of the test is equivalent to logical NOT of the condition i.e., now ages less than or equal to 18 will return true. We have chained the Predicate with negate and directly tested the values without first storing the newly created Predicate, unlike in previous examples.
5. Checking Equality with Predicates in Java 8 Using "isEqual" Method
This type of Predicate, generated by the static method isEqual, is used to check if an object is equal to another object according to Object.equals(Object, Object)
Checking Equality for a Primitive Type
Output:
Explanation: The Predicate in the above code is used to check some string's equality with the string "PIE". isEqual is a static method of Predicate interface, so it is callable without creating any lambda expression beforehand. This is equivalent to Predicate<String> testStringEquality = str -> str == "PI". However, isEqual allows you to do so implicitly.
Checking Equality for an Object Created through a Custom Class that Overrides "equals" Method
Output:
Explanation: This example shows how the isEqual method works. It utilises Object.equals(Object, Object) method internally. The object referenced by p1 and the object passed to the Predicate (for checking the equality) are two different objects created in the Heap. But the equality test done through Predicate still gives a true value because the equals method is overridden in the Person class to check equality only for data members' values, not the object reference. If you didn't override the equals method then the output would be false for both cases.
6. Predicate in Java 8 to Filter a Collection
The most common use of Predicates is found in filtering a collection of objects by creating a stream.
Output:
Explanation: startsWithb Predicate stores the check if strings start with the letter 'b'. hasLengthGreaterThan2 stores the check for strings that contain more than two letters. The composed Predicate in the example acts as a filter rule for a given list of strings. Such filter rules are accepted in the filter method.
Thus, Predicate chaining inside the filter method will result in a composed Predicate that stores a check for strings containing more than two letters starting with the letter 'b'.
Conclusion
- Predicate in Java 8 combined with lambda expressions provide an intuitive way of programming.
- You get a more readable code with Predicates that is easy to manage and maintain.
- Predicate interface is highly flexible for composing new predicates using existing ones.
- Logical operations on Predicates like OR, NOT, AND & NOT can be done using or, not, and & negate methods respectively.
- The isEqual method is used to generate a Predicate that checks for equality with objects and values.
- Functionally filtering a collection using Predicates with streams is very easy.
- Easily add a lot of dynamic behaviour in less code.