Definition
- Java's
java.util.functionpackage stops at two arguments (BiFunction). - A three-argument function must be a custom
@FunctionalInterface. - TriFunction declares
R apply(A a, B b, C c). - Java omits it to avoid combinatorial explosion: each arity needs primitive specialisations.
- For 3+ inputs, consider a parameter object for better scalability.
Priya at the National Bank of India needs a rule that takes three inputs at once: principal, rate, and years. The standard toolkit only sells one and two-input tools, so she makes her own three-input tool.
Understanding the Concepts
How do you define a TriFunction?
Declare a @FunctionalInterface with three type parameters and one abstract method.
package com.bank.core;
@FunctionalInterface
public interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
package com.bank.core;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class TriFunctionDemo {
public static void main(String[] args) {
// Simple interest: principal * rate * years / 100
TriFunction<BigDecimal, BigDecimal, Integer, BigDecimal>
simpleInterest = (principal, rate, years) ->
principal.multiply(rate)
.multiply(BigDecimal.valueOf(years))
.divide(BigDecimal.valueOf(100), 2,
RoundingMode.HALF_UP);
BigDecimal interest = simpleInterest.apply(
new BigDecimal("100000"),
new BigDecimal("8.5"),
3);
System.out.println("Interest: " + interest);
}
}
Interest: 25500.00
Why does Java have no built-in TriFunction?
For each arity, the library would also need primitive specialisations (IntTriFunction, ToDoubleTriFunction, etc.), multiplying the number of interfaces. The designers drew the line at two arguments, which covers the overwhelming majority of real use cases.
| Arity | Interfaces needed (approx) |
|---|---|
| 1 arg | Function, IntFunction, ToIntFunction, ... (40+) |
| 2 args | BiFunction, ToDoubleBiFunction, ... (20+) |
| 3 args | Would need 30+ more interfaces |
When should you use a parameter object instead?
A parameter object bundles multiple inputs into a named class, which scales better and is more evolvable.
// Parameter object
record LoanParams(BigDecimal principal, BigDecimal rate, int years) {}
Function<LoanParams, BigDecimal> interestCalc = p ->
p.principal().multiply(p.rate())
.multiply(BigDecimal.valueOf(p.years()))
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
BigDecimal result = interestCalc.apply(
new LoanParams(new BigDecimal("100000"),
new BigDecimal("8.5"), 3));
| Approach | Scales to more inputs? | Named inputs? | Evolvable? |
|---|---|---|---|
| TriFunction | No (4th arg needs QuadFunction) | No | No |
| Parameter object | Yes | Yes | Yes (add fields) |
What are common mistakes?
Mistake: not adding @FunctionalInterface.
// Without the annotation, someone could add a second abstract method later
// breaking all lambdas. Always add @FunctionalInterface for safety.
What should a senior engineer know?
- A TriFunction is fine for a quick, localised need. For an evolving API, prefer a parameter object.
- Currying is another alternative: convert TriFunction into nested Functions: a -> b -> c -> result.
- Some libraries (Vavr, Apache Commons) provide TriFunction and beyond.
- The trade-off is brevity (TriFunction) vs growth (parameter object).
What are common interview follow-up questions?
- "Can you curry a TriFunction?" Yes: convert to Function>>.
- "Does Vavr library provide TriFunction?" Yes, up to Function8.
- "How do you add andThen to TriFunction?" Add a default method: default
TriFunction andThen(Function after).
Visual Overview
@FunctionalInterface
TriFunction<A, B, C, R> { R apply(A, B, C) }
simpleInterest.apply(100000, 8.5, 3)
│
│ principal * rate * years / 100
▼
100000 * 8.5 * 3 / 100 = 25500.00
Alternative: parameter object
Function<LoanParams, BigDecimal>
│
│ apply(LoanParams(100000, 8.5, 3))
▼
25500.00
Key Takeaway
Java provides Function (1 arg) and BiFunction (2 args) but stops there to avoid combinatorial explosion. When you need three arguments, declare your own @FunctionalInterface TriFunction with R apply(A, B, C) and implement it with a lambda.
At the National Bank of India, a simple-interest calculator took principal, rate, and years: a natural fit for a TriFunction. But for evolving APIs, a parameter object (a record bundling the inputs) scales better, gives named fields, and handles a fourth parameter without needing a QuadFunction.
The trade-off is brevity now (TriFunction) vs growth later (parameter object). For a quick, localised need, a TriFunction is fine. For a public API, prefer the parameter object.
Remember one sentence: Java stops at BiFunction to avoid interface sprawl; define TriFunction yourself for three arguments, or use a parameter object for better scalability.
---