Core functional interfaces in Java
Function, Predicate, Consumer, Supplier, BiFunction, UnaryOperator, BinaryOperator.
filter wants a Predicate. map wants a Function. forEach wants a Consumer. Learn four names and you can read almost any modern Java API signature.Standard plug shapes
java.util.function provides ready-made functional interfaces, so you rarely need your own. Each describes a shape: what goes in and what comes out. Like standard plug sockets, any lambda with the right shape fits.
The big four
**Function<T, R>**: T in, R out, via apply. **Predicate<T>**: T in, boolean out, via test. **Consumer<T>**: T in, nothing out, via accept. **Supplier<T>**: nothing in, T out, via get.
Function<String, Integer> len = s -> s.length();
Predicate<String> empty = s -> s.isEmpty();
Consumer<String> show = s -> IO.println(s);
Supplier<String> hi = () -> "hi";
len.apply("abc"); empty.test(""); hi.get();Do names matter?
A lambda never mentions apply, test or get. So why do you need to know those method names?
Think about it, then reveal the answer
Because the code that uses the function must call it by name: supplier.get(), predicate.test(x), function.apply(x), consumer.accept(x). Writing the lambda is anonymous; calling it isn't.
Your turn
What does this print?
Supplier<String> word = () -> "java";
Predicate<String> isLong = s -> s.length() > 3;
Function<String, Integer> len = s -> s.length();
System.out.println(isLong.test(word.get()));
System.out.println(len.apply(word.get()) * 2);true 8false 4true 4
Show the answer
word.get() supplies "java" (4 letters). isLong.test checks length > 3 → true. len.apply gives 4, times 2 → 8.
Two inputs, same types
**BiFunction<T, U, R> takes two inputs. When input and output types match, use the shortcuts: UnaryOperator<T> extends Function<T, T>, and BinaryOperator<T> extends BiFunction<T, T, T>**.
BiFunction<Integer, Integer, Integer> a =
(x, y) -> x + y; // 3 type args
BinaryOperator<Integer> b =
(x, y) -> x + y; // same shape, 1 arg
UnaryOperator<String> up = s -> s.toUpperCase();Chain them
What does this print?
UnaryOperator<String> shout = s -> s + "!";
BinaryOperator<Integer> add = (a, b) -> a + b;
String loud = shout.apply(shout.apply("hi"));
System.out.println(loud + add.apply(1, 2));hi!3hi!!3hi!!12
Show the answer
shout adds one "!", applied twice → "hi!!". add.apply(1, 2) is 3, and String + Integer concatenates: hi!!3.
Calling the wrong method
Each interface has its own method name. Supplier has **get()**, not supply(). Predicate has test(), not apply(). Mix them up and it won't compile.
Supplier<String> s = () -> "x";
s.supply(); // ✗ no such method
s.get(); // ✓
Predicate<String> p = String::isEmpty;
p.apply(""); // ✗ p.test("") ✓Reading APIs
stream.filter(Predicate), stream.map(Function), list.forEach(Consumer), optional.orElseGet(Supplier), map.computeIfAbsent(key, Function): once these names are second nature, unfamiliar library methods become readable at a glance.
Key takeaways
- Function<T, R>: apply(T) → R; BiFunction<T, U, R>: two inputs
- Predicate<T>: test(T) → boolean
- Consumer<T>: accept(T), returns nothing; Supplier<T>: get() → T
- UnaryOperator<T> = Function<T, T>; BinaryOperator<T> = BiFunction<T, T, T>
The java.util.function package contains 43 interfaces. Most are just variations of the big four for two arguments or for the primitives int, long, double and boolean.
Practice questions
What does this print?
Predicate<String> empty = s -> s.isEmpty();
Supplier<String> hello = () -> "hi";
UnaryOperator<String> up = s -> s.toUpperCase();
System.out.println(empty.test(hello.get()));
System.out.println(up.apply(hello.get()));- true HI
- false HI
- false hi
- hi HI
Check your answer
false HI. hello.get() supplies "hi", which isn't empty, so test returns false. up.apply transforms it to "HI".
Pick the interface that fits.
___<Integer> adder = (a, b) -> a + b;- BinaryOperator
- BiFunction
- UnaryOperator
- Function
Check your answer
BinaryOperator. BinaryOperator<Integer> takes two Integers and returns an Integer. BiFunction would need three type arguments (BiFunction<Integer, Integer, Integer>), and the others take one input.