Predicate Interface

The Predicate interface in Java is a functional interface that represents a single argument function that returns a boolean value. It is part of the java.util.function package and is often used for filtering or matching conditions. This interface is very useful in the context of lambda expressions, as it allows you to define inline logic for evaluating conditions.

Predicate Interface

Definition

The Predicate interface is defined as follows:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    // Default methods for composing predicates
    default Predicate<T> and(Predicate<? super T> other) {
        // implementation
    }

    default Predicate<T> negate() {
        // implementation
    }

    default Predicate<T> or(Predicate<? super T> other) {
        // implementation
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        // implementation
    }
}

Method

  • test(T t): The single abstract method that evaluates the predicate on the given argument.
  • Default Methods:
    • and(Predicate<? super T> other): Returns a composed predicate that represents a short-circuiting logical AND of this predicate and another.
    • negate(): Returns a predicate that represents the logical negation of this predicate.
    • or(Predicate<? super T> other): Returns a composed predicate that represents a short-circuiting logical OR of this predicate and another.
  • Static Method:
    • isEqual(Object targetRef): Returns a predicate that tests if two arguments are equal according to Objects.equals(Object, Object).

Using Predicate with Lambda Expressions

Lambda expressions provide a concise way to create instances of the Predicate interface. Below are various examples demonstrating how to use Predicate with lambda expressions.

Basic Example

Here is a simple example of using a Predicate with a lambda expression to test if a number is even:

import java.util.function.Predicate;

public class PredicateExample {
    public static void main(String[] args) {
        Predicate<Integer> isEven = (n) -> (n % 2 == 0);

        System.out.println(isEven.test(4)); // true
        System.out.println(isEven.test(5)); // false
    }
}

Predicate Chaining

You can use the default methods and, or, and negate to chain predicates.

Example: Combining Predicates
import java.util.function.Predicate;

public class PredicateChainingExample {
    public static void main(String[] args) {
        Predicate<String> isNotNull = (s) -> s != null;
        Predicate<String> isNotEmpty = (s) -> !s.isEmpty();
        Predicate<String> isShortString = (s) -> s.length() < 5;

        Predicate<String> combinedPredicate = isNotNull.and(isNotEmpty).and(isShortString);

        System.out.println(combinedPredicate.test("Test")); // true
        System.out.println(combinedPredicate.test("")); // false
        System.out.println(combinedPredicate.test(null)); // false
        System.out.println(combinedPredicate.test("This is a long string")); // false
    }
}

Predicate with Collections

Predicates are particularly useful when working with collections. They can be used to filter collections using the Stream API.

Example: Filtering a List
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class FilterExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");
        
        Predicate<String> startsWithJ = (s) -> s.startsWith("J");
        List<String> filteredNames = names.stream()
                                          .filter(startsWithJ)
                                          .collect(Collectors.toList());

        System.out.println(filteredNames); // [John, Jane, Jack]
    }
}

Benefits of Using Predicate with Lambda Expressions

  1. Conciseness: Lambda expressions make the code more concise and readable by eliminating the need for boilerplate code.
  2. Reusability: Predicates can be reused and combined in different ways to create complex conditions.
  3. Flexibility: The Predicate interface provides default methods for composing complex predicates, making it flexible to use.
  4. Stream API Integration: Predicates work seamlessly with the Stream API, allowing for efficient and readable data processing.

Example: Real-World Scenario

Consider a scenario where you have a list of users, and you want to filter out those who are adults and have active accounts.

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

class User {
    private String name;
    private int age;
    private boolean active;

    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }

    public int getAge() {
        return age;
    }

    public boolean isActive() {
        return active;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + ", active=" + active + "}";
    }
}

public class UserFilterExample {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("John", 25, true),
            new User("Jane", 17, true),
            new User("Jack", 30, false),
            new User("Doe", 22, true)
        );

        Predicate<User> isAdult = (u) -> u.getAge() >= 18;
        Predicate<User> isActive = (u) -> u.isActive();

        List<User> filteredUsers = users.stream()
                                        .filter(isAdult.and(isActive))
                                        .collect(Collectors.toList());

        filteredUsers.forEach(System.out::println);
    }
}
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

class User {
    private String name;
    private int age;
    private boolean active;

    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }

    public int getAge() {
        return age;
    }

    public boolean isActive() {
        return active;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + ", active=" + active + "}";
    }
}

public class UserFilterExample {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("John", 25, true),
            new User("Jane", 17, true),
            new User("Jack", 30, false),
            new User("Doe", 22, true)
        );

        Predicate<User> isAdult = (u) -> u.getAge() >= 18;
        Predicate<User> isActive = (u) -> u.isActive();

        List<User> filteredUsers = users.stream()
                                        .filter(isAdult.and(isActive))
                                        .collect(Collectors.toList());

        filteredUsers.forEach(System.out::println);
    }
}

In this example, isAdult and isActive are predicates that are combined using and to filter the list of users.

By using the Predicate interface and lambda expressions, you can write clean, concise, and expressive code for evaluating conditions and processing collections.