An easy way to understand Predicate interface in Java 8.

Sameera De Silva
8 min readJan 23, 2024

--

Purpose: Represents a Boolean-valued function, used for testing conditions on values.
Package: java.util.function
Functional Interface: Contains only one abstract method.
Abstract Method:
boolean test(T t): Takes an argument of type T and returns a boolean result based on a condition.
Key Points:
Often used with streams for filtering and conditional operations.
Can be combined using methods like and, or, and negate to create complex conditions.

The methods inside of the Predicate interface

public interface Predicate<T> {

// Abstract method for testing a value
boolean test(T t);

// Default method for composing two predicates with logical AND
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (T t) -> test(t) && other.test(t);
}

// Default method for negating the predicate
default Predicate<T> negate() {
return (T t) -> !test(t);
}

// Default method for composing two predicates with logical OR
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (T t) -> test(t) || other.test(t);
}

// Static method for creating a predicate that tests for equality with a given object


static <T> Predicate<T> isEqual(Object targetRef)

{
return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object);
}
}

Before using Lambdas and streams, let’s look at the traditional way .

package steams.examples;

import java.util.function.Predicate;

// We need to provide Type argument since Predicate interface is public interface Predicate<T>
public class PredicateDemo implements Predicate<Integer> {

// Override the boolean test(T t) Abstract method and pass an Integer value.
@Override
public boolean test(Integer myValue) {

// This returns the boolean value. This checks an Integer divided by 2 is equal to 0 or not.
if (myValue % 2 == 0) {
return true;
} else {
return false;
}
}

public static void main(String[] args) {
PredicateDemo demo=new PredicateDemo();
System.out.println("Is my value get divided by zero=="+demo.test(6));
// Output is Is my value get divided by zero==true
}
}

So now, let’s look at the same code and few more examples, Also I included a Stream example because-

Java Stream API extensively uses the Predicate interface.
In fact, Predicate is one of the fundamental building blocks of Stream operations as it allows for filtering and conditional processing of elements in a stream.

package steams.examples;

import java.util.Arrays;
import java.util.function.Predicate;

public class PredicateDemo {


public static void main(String[] args) {
// Creating a Predicate to check if a given Integer is divisible by 2
Predicate<Integer> divideFunction = myValue -> myValue % 2 == 0;
// Testing the Predicate with a value (6 in this case)
System.out.println("Is my value get divided by zero==" + divideFunction.test(6));
// Output is Is my value get divided by zero==true

// Creating a Predicate to check if a given String is equal to "Hello" (case-insensitive) or holla
Predicate<String> helloPredicate = input -> input.equalsIgnoreCase("Hello") || input.equalsIgnoreCase("Holla");
// Test the function by passing a String value.
System.out.println("Did she say Hello in either English or Spanish==" + helloPredicate.test("hello"));
// Output - Did she say Hello==true


// Creating a Stream of Strings, with a filter that uses the Predicate interface internally. Stream<T> filter(Predicate<? super T> predicate);
String[] words = {"Hello", "HELLO", "Hi", "Hola"};
Arrays.stream(words)
.filter(input -> input.equalsIgnoreCase("Hello"))
.forEach(word -> System.out.println("'" + word + "' is equal to 'Hello'"));
}


}

Output-

Is my value get divided by zero==true
Did she say Hello in either English or Spanish==true
'Hello' is equal to 'Hello'
'HELLO' is equal to 'Hello'

negate() method

It is a default method,
No Arguments: It only needs to be called on an existing Predicate object without requiring any additional input. Predicate<T> negate() {
it also returns the true or false , but the negative scenario of the original condition,
negate() is useful for creating complementary conditions without code duplication.
Assume that Predicate<Integer> isEven = num -> num % 2 == 0;
Because we don’t need to write the negative scenario, just say isEven.negate();
It’s often used with streams to filter elements based on negated criteria.
It demonstrates functional programming concepts in Java.

    default Predicate<T> negate() {
return (t) -> !test(t);
}

Let’s look at a code example which covers below.

Demonstrates the use of the negate() method in the Predicate interface to filter elements in streams based on negated conditions.
Key Steps:

Defines predicates:

isEven: Checks if a number is even.
isOdd: Created using isEven.negate(), checks if a number is odd. This is the negative scenario.
Filters even numbers:

Creates a stream of numbers 1 to 5.
Uses filter(isEven) to keep only even numbers.
Prints the filtered results (2, 4).
Filters odd numbers:

Creates another stream of numbers 1 to 5.
Uses filter(isOdd) to keep only odd numbers.
Prints the filtered results (1, 3, 5).

package steams.examples;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class PredicateNegateExample {
public static void main(String[] args) {
// Original predicate: checks if a number is even
Predicate<Integer> isEven = num -> num % 2 == 0;

// Negated predicate: checks if a number is odd
Predicate<Integer> isOdd = isEven.negate();

Stream.of(1, 2, 3, 4, 5)
.filter(isEven) // Filters even numbers
.forEach(System.out::println); // Prints 2, 4

System.out.println("negate started.");

Stream.of(1, 2, 3, 4, 5)
.filter(isOdd) // Filters odd numbers
.forEach(System.out::println); // Prints 1, 3, 5
}
}

This is a String example for negate method.

package steams.examples;
import java.util.function.Predicate;

public class PredicateNegateStringExample {
public static void main(String[] args) {
// Positive scenario, with logic.
Predicate<String> helloPredicate = input -> input.equalsIgnoreCase("Hello") || input.equalsIgnoreCase("Holla");

// Negated predicate: checks if the input is NOT "Hello" or "Holla" by using positive logic helloPredicate
Predicate<String> notHelloPredicate = helloPredicate.negate();

System.out.println("Positive-Did she say Hello in either English or Spanish? " + helloPredicate.test("hello")); // True
// Use negate
System.out.println("Negative-Did she NOT say Hello in either English or Spanish? " + notHelloPredicate.test("hello")); // False

System.out.println("Positive-Did she say Hello in either English or Spanish? " + helloPredicate.test("Bye")); // False
// Use negate
System.out.println("Negative-Did she NOT say Hello in either English or Spanish? " + notHelloPredicate.test("Bye")); // True
}
}

Output-

Positive-Did she say Hello in either English or Spanish? true
Negative-Did she NOT say Hello in either English or Spanish? false
Positive-Did she say Hello in either English or Spanish? false
Negative-Did she NOT say Hello in either English or Spanish? true

isEqual() Method

The isEqual() method is a static factory method that creates a new Predicate that tests for equality with a given target object.
It uses the Objects.equals(Object, Object) method for equality comparison, ensuring consistent behavior across different types.

Arguments: It takes one argument: targetRef of type Object.
This represents the object that will be used for comparison with other objects when using the returned Predicate.
Return Type: The method returns a Predicate<T> object that can be used for various purposes,
such as filtering collections, checking conditions, or writing concise comparisons in your code.

  static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}

Code Example-

package steams.examples;

import java.util.function.Predicate;

public class PredicateIsEqualMethod {
public static void main(String[] args) {
// Define the target value


// Create a predicate using isEqual()
/*
To use the isEqual() method correctly, you need to assign its result to a Predicate variable:
so can't assign to a boolean
boolean isHello = Predicate.isEqual(targetValue);
*/
// Define the target AKA expected value
Predicate<String> isHello = Predicate.isEqual("Hello");

// Test the predicate with different actual values
System.out.println(isHello.test("Hello")); // Output: true
System.out.println(isHello.test("Hi")); // Output: false

}
}

Another example

package steams.examples;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PredicateStreamStringExample {
public static void main(String[] args) {
// Create a list of names
List<String> names = Arrays.asList("Bob", "Alice", "Charlie");

// Define the target value to filter for
String expectedName = "Alice";

// Create a predicate using isEqual()
Predicate<String> isAlicePredicate = Predicate.isEqual(expectedName);

// Filter the names list using the predicate
List<String> aliceNames = names.stream()
.filter(isAlicePredicate)
.collect(Collectors.toList());

// Print the filtered list
System.out.println("Names that are 'Alice': " + aliceNames); // Output: [Alice]
}
}

The opposite of this method is static <T> Predicate<T> not(Predicate<? super T> target)

The idea of this is quite similar to above explained negate() method.

It is used to negate a given predicate, essentially creating a predicate that represents the opposite logical condition.

   static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}

Functionality:

When you call Predicate.not(target), it creates a new predicate that does the following:
Takes an object of type T as input.
Applies the original target predicate to that object.
Returns the opposite boolean result of what the original predicate would have returned.

A code example

package steams.examples;

import java.util.function.Predicate;

public class PredicateIsEqualMethod {
public static void main(String[] args) {
// Define the target value


// Create a predicate using isEqual()
/*
To use the isEqual() method correctly, you need to assign its result to a Predicate variable:
so can't assign to a boolean
boolean isHello = Predicate.isEqual(targetValue);
*/
// Define the target AKA expected value
Predicate<String> isHello = Predicate.isEqual("Hello");
// Create the negated predicate using Predicate.not()
Predicate<String> isNotHello = Predicate.not(isHello);

// Test the predicate with different actual values for the positive scenario,
System.out.println(isHello.test("Hello")); // Output: true
System.out.println(isHello.test("Hi")); // Output: false

// Test the negated predicate
System.out.println(isNotHello.test("Hello")); // Output: false
System.out.println(isNotHello.test("Hi")); // Output: true

}
}

default Predicate<T> and(Predicate<? super T> other) method in Java’s Predicate interface

Purpose:

It combines two predicates into a single predicate that represents their logical AND condition.
It returns a new predicate that evaluates to true only if both the original predicate and the provided other predicate evaluate to true for a given input.

Signature:

It’s a default method, meaning it has a default implementation within the Predicate interface.
It’s a generic method, parameterized by T to represent the type of objects the predicates test.
It takes one argument: other of type Predicate<? super T>, which is the predicate to be combined with the current predicate using the AND operation.
It returns a new Predicate<T> object that represents the combined predicate.

Functionality:

When you call predicate1.and(predicate2), it creates a new predicate that does the following:
Takes an object of type T as input.
Applies both predicate1 and predicate2 to that object.
Returns true only if both predicates return true. Otherwise, it returns false.

A simple example

import java.util.function.Predicate;

public class CombinedPredicate {
public static void main(String[] args) {
Predicate<Integer> isEven = num -> num % 2 == 0;
Predicate<Integer> isGreaterThan5 = num -> num > 5;

Predicate<Integer> isEvenAndGreaterThan5 = isEven.and(isGreaterThan5);

System.out.println(isEvenAndGreaterThan5.test(4)); // false (even but not greater than 5)
System.out.println(isEvenAndGreaterThan5.test(6)); // true (both even and greater than 5)
System.out.println(isEvenAndGreaterThan5.test(7)); // false (odd)
}
}

An Example with Stream

package steams.examples;

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

public class PredicateCombineExample {

public static void main(String[] args) {
// Create a list of numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Define two predicates with 2 logics.
Predicate<Integer> isEven = num -> num % 2 == 0;
Predicate<Integer> isGreaterThan5 = num -> num > 5;

// Combine two predicates using `and`
Predicate<Integer> isEvenAndGreaterThan5 = isEven.and(isGreaterThan5);

// Filter the list using the combined predicate and stream
List<Integer> filteredNumbers = numbers.stream()
.filter(isEvenAndGreaterThan5)
.collect(Collectors.toList());

// Print the filtered numbers
System.out.println("Numbers that are even and greater than five: " + filteredNumbers);
}
}

Output-

Numbers that are even and greater than five: [6, 8, 10]

Further reference-https://www.youtube.com/watch?v=Tapz6_T5oHY&t=676s

--

--

No responses yet