Predicate / Function / Consumer / Supplier
| Since: | Java 8(2014) |
|---|
These are the primary functional interfaces added in Java 8. They are used in combination with lambda expressions and method references, each serving one of four roles: testing, transforming, consuming, or supplying values. To summarize: no arguments → Supplier; arguments with a return value → Function; arguments with no return value → Consumer; arguments returning a boolean → Predicate.
Syntax
// Predicate<T>: Takes a T and returns a boolean.
Predicate<T> pred = (T t) -> /* boolean expression */;
boolean result = pred.test(t);
// Function<T, R>: Takes a T and returns an R.
Function<T, R> func = (T t) -> /* value of type R */;
R result = func.apply(t);
// Consumer<T>: Takes a T and returns nothing.
Consumer<T> cons = (T t) -> { /* process */ };
cons.accept(t);
// Supplier<T>: Takes no arguments and returns a T.
Supplier<T> supp = () -> /* value of type T */;
T value = supp.get();
// BiFunction<T, U, R>: Takes two arguments and returns an R.
BiFunction<T, U, R> bif = (T t, U u) -> /* value of type R */;
// UnaryOperator<T>: Takes a T and returns a T of the same type.
UnaryOperator<T> op = (T t) -> /* value of type T */;
Common Functional Interfaces
| Interface | Abstract Method | Description |
|---|---|---|
| Predicate<T> | test(T t) → boolean | Used for condition testing. Passed to stream operations like filter(). |
| Function<T, R> | apply(T t) → R | Transforms a value. Passed to stream operations like map(). |
| Consumer<T> | accept(T t) → void | Accepts a value and processes it without returning anything. Passed to operations like forEach(). |
| Supplier<T> | get() → T | Produces and returns a value with no arguments. Used for lazy evaluation. |
| BiFunction<T, U, R> | apply(T t, U u) → R | Accepts two arguments and returns a value. |
| BiConsumer<T, U> | accept(T t, U u) → void | Accepts two arguments and processes them. Passed to operations like Map.forEach(). |
| UnaryOperator<T> | apply(T t) → T | Performs a transformation within the same type. A specialization of Function<T,T>. |
| BinaryOperator<T> | apply(T t, T u) → T | Accepts two arguments of the same type and returns the same type. Passed to operations like reduce(). |
Sample Code
FunctionalInterfaces.java
import java.util.*;
import java.util.function.*;
class FunctionalInterfaces {
public static void main(String[] args) {
// Predicate: Tests whether a number is even.
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // Prints "true".
System.out.println(isEven.test(7)); // Prints "false".
// Composing Predicates: Combine with and() / or() / negate().
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
System.out.println(isEvenAndPositive.test(4)); // Prints "true".
System.out.println(isEvenAndPositive.test(-2)); // Prints "false".
// Function: Converts a string to uppercase.
Function<String, String> toUpper = String::toUpperCase;
System.out.println(toUpper.apply("hello")); // Prints "HELLO".
// Composing Functions: Chain with andThen().
Function<String, Integer> toLength = String::length;
Function<String, Integer> upperLength = toUpper.andThen(toLength);
System.out.println(upperLength.apply("hello")); // Prints "5".
// Consumer: Prints each element in a list.
Consumer<String> printer = s -> System.out.println("> " + s);
List.of("A", "B", "C").forEach(printer);
// Supplier: Lazily creates a default value.
Supplier<List<String>> listFactory = ArrayList::new;
List<String> newList = listFactory.get();
newList.add("item");
System.out.println(newList); // Prints "[item]".
}
}
The command looks like this:
javac FunctionalInterfaces.java java FunctionalInterfaces true false true false HELLO 5 > A > B > C [item]
Common Mistakes
Common Mistake 1: Throwing a checked exception inside a lambda and getting a compile error
Standard functional interfaces such as Runnable and Consumer do not declare checked exceptions. Throwing IOException or similar inside a lambda assigned to these types causes a compile error.
FunctionalInterfacesNg1.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
class FunctionalInterfacesNg1 {
public static void main(String[] args) {
Consumer<String> writer = filename -> {
Files.writeString(Path.of(filename), "Gojo Satoru's technique notes");
};
writer.accept("memo.txt");
}
}
The command looks like this:
javac FunctionalInterfacesNg1.java
java FunctionalInterfacesNg1
FunctionalInterfacesNg1.java:9: error: unreported exception IOException; must be caught or declared to be thrown
Files.writeString(Path.of(filename), "Gojo Satoru's technique notes");
^
Wrap the checked exception in a try-catch inside the lambda, or define a custom functional interface that declares the checked exception.
FunctionalInterfacesOk1.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
class FunctionalInterfacesOk1 {
public static void main(String[] args) {
Consumer<String> writer = filename -> {
try {
Files.writeString(Path.of(filename), "Gojo Satoru's technique notes");
System.out.println("Write completed: " + filename);
} catch (IOException e) {
System.err.println("Write error: " + e.getMessage());
}
};
writer.accept("memo.txt");
}
}
The command looks like this:
javac FunctionalInterfacesOk1.java java FunctionalInterfacesOk1 Write completed: memo.txt
Common Mistake 2: Confusing Consumer<T> with Function<T,R> and getting a compile error
Consumer<T> has no return value (void). Assigning a lambda that returns a value to a Consumer<T> variable causes a compile error. Use Function<T,R> when you need a return value.
FunctionalInterfacesNg2.java
import java.util.function.Consumer;
class FunctionalInterfacesNg2 {
public static void main(String[] args) {
Consumer<String> getName = name -> "Sorcerer: " + name;
System.out.println(getName.accept("Itadori Yuji"));
}
}
The command looks like this:
javac FunctionalInterfacesNg2.java
java FunctionalInterfacesNg2
FunctionalInterfacesNg2.java:5: error: incompatible types: bad return type in lambda expression
Consumer<String> getName = name -> "Sorcerer: " + name;
^
Use Function<T,R> when the lambda returns a value. Consumer<T>'s accept() returns void, so lambdas that produce a result cannot be used with it.
FunctionalInterfacesOk2.java
import java.util.function.Function;
import java.util.function.Consumer;
class FunctionalInterfacesOk2 {
public static void main(String[] args) {
Function<String, String> getName = name -> "Sorcerer: " + name;
System.out.println(getName.apply("Itadori Yuji"));
Consumer<String> printer = name -> System.out.println("Sorcerer: " + name);
printer.accept("Kugisaki Nobara");
}
}
The command looks like this:
javac FunctionalInterfacesOk2.java java FunctionalInterfacesOk2 Sorcerer: Itadori Yuji Sorcerer: Kugisaki Nobara
Notes
These interfaces are part of the java.util.function package and are used as the target types for lambda expressions and method references. They are also widely used in the Stream API and Optional, so learning them will be very useful.
Predicate supports composition via and() / or() / negate(), and Function supports composition via andThen() / compose(). Using primitive-specialized variants such as IntPredicate, IntFunction<R>, IntConsumer, and IntSupplier avoids the cost of autoboxing.
For how to write lambda expressions, see 'Lambda Expressions / (x) -> x * 2'.
If you find any errors or copyright issues, please contact us.