On this page
Functional Interfaces
The java.util.function package (since Java 8) provides standard functional interfaces used throughout the Stream API and modern Java libraries.
Core Interfaces
| Interface | Method | Description |
|---|---|---|
Function<T,R> |
R apply(T t) |
Transform T to R |
Consumer<T> |
void accept(T t) |
Consume T, return nothing |
Supplier<T> |
T get() |
Supply a T |
Predicate<T> |
boolean test(T t) |
Test T, return boolean |
BiFunction<T,U,R> |
R apply(T t, U u) |
Transform two inputs to R |
BiConsumer<T,U> |
void accept(T t, U u) |
Consume two values |
BiPredicate<T,U> |
boolean test(T t, U u) |
Test two values |
Examples
Function<String, Integer> length = String::length;
System.out.println(length.apply("hello")); // 5
Consumer<String> print = System.out::println;
print.accept("Hello World");
Supplier<Double> random = Math::random;
System.out.println(random.get());
Predicate<String> isEmpty = String::isEmpty;
System.out.println(isEmpty.test("")); // true
System.out.println(isEmpty.test("a")); // false
Primitive Specializations
Avoid boxing overhead with primitive-specific interfaces:
| Interface | Type |
|---|---|
IntFunction<R> |
int → R |
ToIntFunction<T> |
T → int |
IntPredicate |
test int |
IntConsumer |
accept int |
IntSupplier |
supply int |
ObjIntConsumer<T> |
accept T and int |
IntUnaryOperator square = x -> x * x;
System.out.println(square.applyAsInt(5)); // 25
Operator Interfaces
Specializations of Function where input and output types are the same:
UnaryOperator<String> upper = String::toUpperCase;
BinaryOperator<Integer> max = Integer::max;
List<Integer> numbers = List.of(3, 1, 4, 1, 5);
Integer result = numbers.stream().reduce(max).orElse(0); // 5
Composition Methods
Functional interfaces provide default methods for chaining:
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add3 = x -> x + 3;
Function<Integer, Integer> combined = multiplyBy2.andThen(add3);
System.out.println(combined.apply(5)); // (5 * 2) + 3 = 13
Function<Integer, Integer> reversed = add3.compose(multiplyBy2);
System.out.println(reversed.apply(5)); // (5 + 3) * 2 = 16
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> longEnough = s -> s.length() > 5;
Predicate<String> valid = notEmpty.and(longEnough);
@FunctionalInterface
Always annotate custom functional interfaces:
@FunctionalInterface
public interface Validator<T> {
boolean validate(T value);
// Default and static methods don't count toward the abstract method limit
default Validator<T> and(Validator<T> other) {
return value -> this.validate(value) && other.validate(value);
}
}
Custom vs Built-in
Use built-in interfaces when signatures match. Create custom ones when you need domain-specific names or additional default methods:
@FunctionalInterface
public interface EventHandler {
void handle(Event event);
}
Best Practices
- Prefer
java.util.functiontypes over custom interfaces when possible - Use primitive specializations in performance-critical code
- Use
andThen/composefor readable pipeline composition - Annotate custom functional interfaces with
@FunctionalInterface