λ Lambdas & Functional Java · Intermediate

Higher-order functions in Java

Methods that take or return functions; strategy via lambdas.

🧩 The mysteryA method that builds custom functions on demand. multiplier(3) makes a tripler; multiplier(10) makes a ten-timer. Each remembers its own number. How?

Functions as values

A higher-order function takes a function as a parameter, returns one, or both. You already use them: list.sort(comparator), map.computeIfAbsent(key, f), f.andThen(g).

Taking a function

Pass the behaviour in as a parameter. The method decides when to run it; the caller decides what it does.

static int twice(IntUnaryOperator f, int x) {
    return f.applyAsInt(f.applyAsInt(x));
}
twice(n -> n * n, 3);  // (3²)² = 81
🔮 Predict it

Your turn

Using that twice, what does this print?

static int twice(IntUnaryOperator f, int x) {
    return f.applyAsInt(f.applyAsInt(x));
}
void main() {
    System.out.println(twice(n -> n + 10, 1));
}
  1. 11
  2. 21
  3. 20
Show the answer

The function runs on 1 (giving 11), then on the result: 11 + 10 = 21.

Returning a function

A method can build and return a lambda. The returned lambda captures the parameters of the call that created it, so each one remembers its own value.

static Function<Integer, Integer> adder(int k) {
    return x -> x + k;   // captures this k
}
var plus5 = adder(5);
plus5.apply(1);   // 6
🔮 Predict it

Two factories, two memories

What does this print?

static Function<Integer, Integer> adder(int k) {
    return x -> x + k;
}
void main() {
    var plus5 = adder(5);
    var plus100 = adder(100);
    IO.println(plus5.apply(plus100.apply(1)));
}
  1. 106
  2. 16
  3. 105
Show the answer

plus100.apply(1) is 101 (its k is 100). plus5.apply(101) is 106 (its k is 5). Each lambda captured k from its own call.

Choosing a discount at runtime

✗ If/else everywhere
double pay(double p, String rule) {
    if (rule.equals("ten")) return p * 0.9;
    if (rule.equals("five")) return p - 5;
    return p;  // and copied elsewhere…
}

Every new rule means editing this method, and every copy of it.

✓ Pass the strategy
double pay(double p, DoubleUnaryOperator d) {
    return d.applyAsDouble(p);
}
pay(100, x -> x * 0.9);

Each rule is just "price in, price out". This is the Strategy pattern, minus the extra classes.

A table of strategies

Functions can live in variables and maps like any value: a lookup table of operations, picked by name at runtime.

Map<String, IntBinaryOperator> ops =
    new TreeMap<>();
ops.put("add", (a, b) -> a + b);
ops.put("mul", (a, b) -> a * b);
ops.get("mul").applyAsInt(6, 3);  // 18
💼 In the real world

In real systems

Retry helpers (retry(3, () -> callApi())), web-framework middleware, event handlers, validation rules and pricing engines are all built from higher-order functions. They let you add new behaviour without touching the code that runs it.

Key takeaways

  1. Takes a function: list.sort(comparator), map.computeIfAbsent(k, f)
  2. Returns a function: a factory like multiplier(3)
  3. Returned lambdas capture the factory's parameters
  4. Strategy pattern: store functions in a variable or a map
🤯 Did you know?

Comparator.comparing is itself a higher-order function: it takes a key-extracting function and returns a brand-new Comparator, which is itself a function.

Practice questions

What does this print?

static Function<Integer, Integer> multiplier(int k) {
    return x -> x * k;
}
void main() {
    var triple = multiplier(3);
    var times10 = multiplier(10);
    System.out.println(triple.apply(times10.apply(2)));
}
  1. 60
  2. 15
  3. 32
  4. 23
Check your answer

60. Each call returns a new lambda that remembers its own k. times10.apply(2) is 20, and triple.apply(20) is 60.

What does this print?

static int applyTwice(IntUnaryOperator f, int x) {
    return f.applyAsInt(f.applyAsInt(x));
}
void main() {
    System.out.println(applyTwice(n -> n * n, 3));
}
  1. 9
  2. 81
  3. 6
  4. 27
Check your answer

81. The function is applied to 3 (giving 9), then to the result: 9 * 9 = 81.

Final lesson of the world: sort people by name with nulls first, newest first, in one readable line. Plus the one place where lambdas mysteriously lose their types.