An interface with exactly one abstract method — the target type that every lambda and method reference is converted to.
Declaring one
public class Main {
@FunctionalInterface
interface Calculator {
int apply(int a, int b); // the single abstract method
default Calculator twice() { // defaults are allowed
return (a, b) -> apply(apply(a, b), b);
}
}
public static void main(String[] args) {
Calculator add = (a, b) -> a + b;
Calculator mul = (a, b) -> a * b;
System.out.println(add.apply(3, 4));
System.out.println(mul.apply(3, 4));
System.out.println(add.twice().apply(3, 4)); // (3 + 4) + 4
}
}7
12
11@FunctionalInterface
interface Broken {
void first();
void second(); // error: Broken is not a functional interface
}The java.util.function toolbox
You rarely need to declare your own — the JDK ships 43 standard shapes. Learn the six core ones; the rest are variations.
| Interface | Method | Shape | Typical use |
|---|---|---|---|
Predicate<T> | boolean test(T t) | T → boolean | filter, removeIf |
Function<T, R> | R apply(T t) | T → R | map, computeIfAbsent |
Consumer<T> | void accept(T t) | T → nothing | forEach |
Supplier<T> | T get() | nothing → T | lazy defaults, orElseGet, factories |
UnaryOperator<T> | T apply(T t) | T → T | replaceAll, Stream.iterate |
BinaryOperator<T> | T apply(T a, T b) | (T, T) → T | reduce, merge |
BiFunction<T, U, R> | R apply(T t, U u) | (T, U) → R | Map.compute, replaceAll on maps |
BiConsumer<T, U> / BiPredicate<T, U> | accept / test | two inputs | Map.forEach |
Primitive specializations avoid boxing: IntPredicate, IntFunction<R>, ToIntFunction<T>, IntUnaryOperator, IntBinaryOperator, IntSupplier, IntConsumer (plus Long* / Double* versions, and ObjIntConsumer<T>).
import java.util.function.*;
public class Main {
public static void main(String[] args) {
Predicate<String> isEmpty = String::isEmpty;
Function<String, Integer> length = String::length;
Consumer<String> shout = s -> System.out.println(s.toUpperCase());
Supplier<String> greeting = () -> "hello";
UnaryOperator<Integer> square = x -> x * x;
BinaryOperator<Integer> max = Math::max;
IntBinaryOperator add = (a, b) -> a + b; // no boxing
System.out.println(isEmpty.test(""));
System.out.println(length.apply("lambda"));
shout.accept("java");
System.out.println(greeting.get());
System.out.println(square.apply(7) + " " + max.apply(3, 9) + " " + add.applyAsInt(2, 3));
}
}true
6
JAVA
hello
49 9 5Composition
The standard interfaces have default methods that build new functions out of old ones — no new classes needed.
| Method | On | Meaning |
|---|---|---|
f.andThen(g) | Function, Consumer | run f, then g on the result: g(f(x)) |
f.compose(g) | Function | run g first: f(g(x)) |
p.and(q) / p.or(q) / p.negate() | Predicate | logical combination (short-circuits) |
Predicate.not(p) | Predicate (static, 11+) | negate a method ref: filter(not(String::isBlank)) |
Function.identity() | Function (static) | x -> x |
import java.util.function.*;
public class Main {
public static void main(String[] args) {
Function<Integer, Integer> plus2 = x -> x + 2;
Function<Integer, Integer> times3 = x -> x * 3;
System.out.println(plus2.andThen(times3).apply(1)); // (1 + 2) * 3
System.out.println(plus2.compose(times3).apply(1)); // (1 * 3) + 2
Predicate<String> longWord = s -> s.length() > 4;
Predicate<String> startsJ = s -> s.startsWith("J");
System.out.println(longWord.and(startsJ).test("Java"));
System.out.println(longWord.or(startsJ).test("Java"));
System.out.println(longWord.negate().test("Java"));
}
}9
5
false
true
trueWhat makes an interface functional?
Why do primitive specializations like IntPredicate exist?
Function vs UnaryOperator vs BinaryOperator?