java-interfaces-funcionales-predicate-consumer-function-supplier

Functional Interfaces: Predicate, Consumer, Function, and Supplier

  • 3 min

A functional interface is an interface with a single abstract method that can be implemented using a lambda.

In the previous article we learned how to write arrows (->) to express short behaviors.

But, if I write: s -> s.length()

What type is that? What variable do I store it in? Before Java 8, we had to invent our own interfaces like Calculator or Printable. But Java 8 said: “Stop reinventing the wheel. Let’s create a standard.”

Thus the package java.util.function was born. Here live several predefined interfaces that cover many common cases. We will focus on four: Predicate, Consumer, Function, and Supplier.

Predicate <T>

A Predicate is used to evaluate a condition. It takes an object and says YES or NO (true/false).

  • Accepts: An object T.
  • Returns: boolean.
  • Abstract method: test(T t).
// Definition: A predicate that decides if a String is long
Predicate<String> isLong = text -> text.length() > 5;

// Usage:
boolean result = isLong.test("Hello World"); // true
boolean result2 = isLong.test("Java");       // false
Copied!

Real-world Use: It is the king of the .filter() method in Streams (filtering lists).

Consumer <T>

A Consumer is used to perform an action on an object. It consumes the object, does something with it (print it, save it to a database, send it) and returns nothing.

  • Accepts: An object T.
  • Returns: void (Nothing).
  • Abstract method: accept(T t).
// Definition: A consumer that prints in uppercase
Consumer<String> shouter = text -> System.out.println(text.toUpperCase());

// Usage:
shouter.accept("hello"); // Prints: "HELLO"
Copied!

Real-world Use: It is the king of the .forEach() method (iterating over lists).

Function <T, R>

A Function is used to convert one thing into another. It takes raw material (T) and returns a processed product (R).

  • Accepts: An object T.
  • Returns: An object R (Result).
  • Abstract method: apply(T t).
// Definition: Transforms a String (T) into its Integer length (R)
Function<String, Integer> length = text -> text.length();

// Usage:
Integer len = length.apply("Java"); // 4
Copied!

Real-world Use: It is the king of the .map() method (transforming data).

Supplier <T>

A Supplier is used to generate values out of thin air. It receives nothing, but delivers something. It is like a pipeline from which objects come out.

  • Accepts: Nothing ().
  • Returns: An object T.
  • Abstract method: get().
// Definition: Generates a random number
Supplier<Double> random = () -> Math.random();

// Usage:
Double n = random.get();
Copied!

Real-world Use: It is heavily used in Lazy initialization and in Optional.orElseGet() (to create a default object only if needed).

“Bi” and Primitive Variants

Sometimes we need functions that accept two arguments. Java adds the prefix Bi.

  • BiFunction<T, U, R>: Accepts T and U, returns R. (Example: Adding two numbers).
  • BiConsumer<T, U>: Accepts T and U, returns nothing. (Example: Putting key-value in a Map).
  • BiPredicate<T, U>: Accepts T and U, returns boolean.

And to avoid the overhead of Wrappers (Integer), there are versions for primitives: IntPredicate, LongFunction, etc.