Higher-order functions in Java
Methods that take or return functions; strategy via lambdas.
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²)² = 81Your 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));
}112120
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); // 6Two 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)));
}10616105
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
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.
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); // 18In 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
- Takes a function: list.sort(comparator), map.computeIfAbsent(k, f)
- Returns a function: a factory like multiplier(3)
- Returned lambdas capture the factory's parameters
- Strategy pattern: store functions in a variable or a map
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)));
}- 60
- 15
- 32
- 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));
}- 9
- 81
- 6
- 27
Check your answer
81. The function is applied to 3 (giving 9), then to the result: 9 * 9 = 81.