λ Lambdas & Functional Java · Intermediate

Core functional interfaces in Java

Function, Predicate, Consumer, Supplier, BiFunction, UnaryOperator, BinaryOperator.

🧩 The mysteryfilter 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();
🤔 Think first

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.

🔮 Predict it

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);
  1. true 8
  2. false 4
  3. true 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();
🔮 Predict it

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));
  1. hi!3
  2. hi!!3
  3. hi!!12
Show the answer

shout adds one "!", applied twice → "hi!!". add.apply(1, 2) is 3, and String + Integer concatenates: hi!!3.

⚠️ The trap

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("") ✓
💼 In the real world

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

  1. Function<T, R>: apply(T) → R; BiFunction<T, U, R>: two inputs
  2. Predicate<T>: test(T) → boolean
  3. Consumer<T>: accept(T), returns nothing; Supplier<T>: get() → T
  4. UnaryOperator<T> = Function<T, T>; BinaryOperator<T> = BiFunction<T, T, T>
🤯 Did you know?

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()));
  1. true HI
  2. false HI
  3. false hi
  4. 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;
  1. BinaryOperator
  2. BiFunction
  3. UnaryOperator
  4. 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.

Next: every Integer in a Function<Integer, Integer> is a little object. In a hot loop, that adds up. Meet the primitive versions.