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
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"
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
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();
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.