Last Updated on September 15, 2024
A predicate, in mathematical terms, is a function that returns a boolean value. In the context of Java 8, a Java Predicates are a functional interface that represents a boolean-valued function of one argument. It’s part of the java.util.function package and is designed to be used as the assignment target for lambda expressions or method references.
The introduction of Predicates in Java 8 was motivated by the need to make the language more amenable to functional programming paradigms. Prior to Java 8, implementing simple boolean checks often required verbose anonymous inner classes. With Predicates, developers can now express these checks more succinctly and readably using lambda expressions or method references.
Structure of Predicates
The Predicate interface is defined in the java.util.function package and has the following structure.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
// Default methods
default Predicate<T> and(Predicate<? super T> other) { ... }
default Predicate<T> or(Predicate<? super T> other) { ... }
default Predicate<T> negate() { ... }
// Static methods
static <T> Predicate<T> isEqual(Object targetRef) { ... }
static <T> Predicate<T> not(Predicate<? super T> target) { ... } // Added in Java 11
}
@FunctionalInterface annotation indicates that Predicate is a functional interface, meaning it has exactly one abstract method. This allows Predicate to be used with lambda expressions and method references.
The Predicate interface is generic, allowing it to work with any type of object.
The the single abstract method of the Predicate interface is test(T t) method. It takes an object of type T as input and returns a boolean value. This method encapsulates the boolean-valued function that the Predicate represents.
Default methods:
- and(Predicate<? super T> other): Returns a composed Predicate that represents a short-circuiting logical AND of this Predicate and another.
- or(Predicate<? super T> other): Returns a composed Predicate that represents a short-circuiting logical OR of this Predicate and another.
- negate(): Returns a Predicate that represents the logical negation of this Predicate.
The default methods (and(), or(), negate()) allow for easy composition of complex conditions from simpler ones, promoting modularity and reusability.
Static method:
- isEqual(Object targetRef): Returns a Predicate that tests if two arguments are equal according to Objects.equals(Object, Object).
Example Usage
Let’s explore some code examples to illustrate the use of Predicates in various scenarios
Basic Usage
In this example, we create two simple Predicates: one that checks if a number is positive, and another that checks if a string is empty.
import java.util.function.Predicate;
public class BasicPredicateExample {
public static void main(String[] args) {
// Creating a Predicate using lambda expression
Predicate<Integer> isPositive = n -> n > 0;
// Using the Predicate
System.out.println(isPositive.test(5)); // true
System.out.println(isPositive.test(-3)); // false
System.out.println(isPositive.test(0)); // false
// Creating a Predicate using method reference
Predicate<String> isEmpty = String::isEmpty;
System.out.println(isEmpty.test("")); // true
System.out.println(isEmpty.test("Hello")); // false
}
}
Combining Predicates
This example demonstrates how to combine Predicates using the and(), or(), and negate() methods. These methods allow you to create more complex conditions from simpler ones.
import java.util.function.Predicate;
public class CombiningPredicatesExample {
public static void main(String[] args) {
Predicate<String> hasLengthOf10 = s -> s.length() == 10;
Predicate<String> startWithA = s -> s.startsWith("A");
// Combining Predicates using and()
Predicate<String> hasLengthOf10AndStartsWithA = hasLengthOf10.and(startWithA);
System.out.println(hasLengthOf10AndStartsWithA.test("Abcdefghij")); // true
System.out.println(hasLengthOf10AndStartsWithA.test("Abcdefgh")); // false
System.out.println(hasLengthOf10AndStartsWithA.test("Bbcdefghij")); // false
// Combining Predicates using or()
Predicate<String> hasLengthOf10OrStartsWithA = hasLengthOf10.or(startWithA);
// true
System.out.println(hasLengthOf10OrStartsWithA.test("Abcdefghij"));
// true
System.out.println(hasLengthOf10OrStartsWithA.test("Abcdefgh"));
// true
System.out.println(hasLengthOf10OrStartsWithA.test("Bbcdefghij"));
// false
System.out.println(hasLengthOf10OrStartsWithA.test("Bbcdefgh"));
// Using negate()
Predicate<String> doesNotStartWithA = startWithA.negate();
System.out.println(doesNotStartWithA.test("Apple")); // false
System.out.println(doesNotStartWithA.test("Banana")); // true
}
}
Predicates with Collections
Predicates can be used effectively with the Stream API to filter collections.
It demonstrates the power of combining functional programming concepts with Predicates for concise and expressive code
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PredicateWithCollectionsExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Adam", "Eve", "Alice", "Bob");
// Predicate to check if a name starts with 'J'
Predicate<String> startsWithJ = name -> name.startsWith("J");
// Using Predicate with Stream API
List<String> namesStartingWithJ = names.stream()
.filter(startsWithJ)
.collect(Collectors.toList());
System.out.println("Names starting with J: " + namesStartingWithJ);
// Combining multiple conditions
Predicate<String> lengthGreaterThan3 = name -> name.length() > 3;
Predicate<String> startsWithJAndLengthGreaterThan3 = startsWithJ.and(lengthGreaterThan3);
List<String> filteredNames = names.stream()
.filter(startsWithJAndLengthGreaterThan3)
.collect(Collectors.toList());
System.out.println("Names starting with J and length > 3: " + filteredNames);
}
}
Custom Objects
Predicates can be used to filter custom objects.
This example shows the creation of multiple Predicates for different criteria and how they can be combined for more complex filtering operations.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class CustomObjectFilteringExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35),
new Person("David", 40),
new Person("Eve", 45)
);
// Predicate to filter adults (age >= 18)
Predicate<Person> isAdult = person -> person.getAge() >= 18;
// Predicate to filter seniors (age >= 60)
Predicate<Person> isSenior = person -> person.getAge() >= 60;
// Predicate to filter people with names starting with 'A' or 'B'
Predicate<Person> nameStartsWithAOrB = person ->
person.getName().startsWith("A") ||
person.getName().startsWith("B");
// Combining predicates
Predicate<Person> isAdultWithNameStartingAOrB =
isAdult.and(nameStartsWithAOrB);
List<Person> filteredPeople = people.stream()
.filter(isAdultWithNameStartingAOrB)
.collect(Collectors.toList());
System.out.println("Adults with names starting with A or B: " + filteredPeople);
// Using negate() to find non-seniors
List<Person> nonSeniors = people.stream()
.filter(isSenior.negate())
.collect(Collectors.toList());
System.out.println("Non-seniors: " + nonSeniors);
}
}
Conclusion
Predicates provide a powerful and flexible way to express boolean-valued functions, enabling more concise and expressive code, particularly when working with collections and the Stream API.
Effective use of Predicates can lead to more maintainable, readable, and efficient code. They are particularly valuable in data processing tasks, validation logic, and anywhere complex boolean conditions need to be expressed and manipulated.
As Java continues to evolve, Predicates remain a fundamental part of its functional programming toolkit. Understanding and leveraging Predicates can significantly enhance a Java developer’s ability to write clean, efficient, and expressive code.