Definition
- BiFunction
has R apply(T t, U u)taking two inputs and returning a result. - A raise function takes (Employee, percentage) and returns a new Employee with the higher salary.
- Returning a new object keeps the function pure: no mutation, safe in streams and concurrency.
- .andThen(Function) chains a post-processing step on the result.
- For exact money, use BigDecimal arithmetic.
Priya at the National Bank of India has a rule that takes an employee and a raise percentage and hands back that employee with a bumped-up salary. Two things in, one updated record out.
Understanding the Concepts
How do you write a salary raise BiFunction?
BiFunction takes two arguments and returns a result. Here it builds a new employee with the raised salary.
package com.bank.hr;
import java.util.function.BiFunction;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class SalaryRaise {
public static void main(String[] args) {
BiFunction<Employee, Double, Employee> raise = (e, pct) -> {
BigDecimal current = e.getSalary();
BigDecimal factor = BigDecimal.ONE.add(
BigDecimal.valueOf(pct).divide(
BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP));
BigDecimal newSalary = current.multiply(factor)
.setScale(2, RoundingMode.HALF_UP);
return new Employee(e.getName(), e.getDepartment(), newSalary);
};
Employee rajesh = new Employee("Rajesh", "Loans",
new BigDecimal("75000.00"));
Employee raised = raise.apply(rajesh, 10.0);
System.out.println(rajesh.getName() + ": " + rajesh.getSalary());
System.out.println("After 10% raise: " + raised.getSalary());
}
}
Rajesh: 75000.00
After 10% raise: 82500.00
Why return a new Employee instead of mutating?
Returning a new object makes the function pure and referentially transparent: calling it with the same inputs always produces the same output without changing existing objects. This is safe in streams and concurrent code.
// WRONG: mutation
BiFunction<Employee, Double, Employee> mutating = (e, pct) -> {
e.setSalary(e.getSalary().multiply(factor)); // modifies original!
return e;
};
// CORRECT: immutable
BiFunction<Employee, Double, Employee> immutable = (e, pct) -> {
return new Employee(e.getName(), e.getDepartment(), newSalary);
};
How do you chain with andThen?
import java.util.function.Function;
Function<Employee, String> toAuditLog = e ->
"RAISE: " + e.getName() + " now earns " + e.getSalary();
BiFunction<Employee, Double, String> raiseAndLog =
raise.andThen(toAuditLog);
System.out.println(raiseAndLog.apply(rajesh, 10.0));
RAISE: Rajesh now earns 82500.00
What are common mistakes?
Mistake: using double for salary arithmetic.
// WRONG: double introduces rounding errors
BiFunction<Employee, Double, Employee> wrongRaise = (e, pct) ->
new Employee(e.getName(), e.getDepartment(),
e.getSalaryAsDouble() * (1 + pct / 100));
// 75000.0 * 1.1 might produce 82499.99999999999
// CORRECT: use BigDecimal
// (as shown in the main example above)
What should a senior engineer know?
- BiFunction is the two-argument sibling of Function. Java provides it because many real operations need two inputs.
- Modelling the raise as a BiFunction makes the policy a first-class value: pass it around, test it, swap it.
- .andThen() on BiFunction takes a Function (not another BiFunction) to post-process the result.
- For three or more arguments, create a parameter object or a custom functional interface.
What are common interview follow-up questions?
- "What if you need Employee, Double, and String as inputs?" Use a TriFunction or a parameter object.
- "How is BiFunction different from BinaryOperator?" BinaryOperator
is a BiFunction where all types are the same. - "Can BiFunction be used in Map.merge?" Indirectly; Map.merge takes a BiFunction
(BinaryOperator).
Visual Overview
BiFunction<Employee, Double, Employee>
│
│ apply(Rajesh:75000, 10.0)
▼
Compute:
current = 75000.00
factor = 1 + (10 / 100) = 1.10
newSalary = 75000.00 * 1.10 = 82500.00
│
▼
new Employee("Rajesh", "Loans", 82500.00)
(original Employee unchanged)
Key Takeaway
A BiFunction takes two inputs and returns a result, making it the natural type for a salary raise: accept an employee and a percentage, return a new employee with the increased salary. The key design decision is returning a new object rather than mutating, which keeps the function pure and safe in streams.
For exact salary calculations at the National Bank of India, always use BigDecimal with explicit rounding. Never compute salaries with double multiplication because double arithmetic introduces rounding errors.
Modelling the raise as a BiFunction makes it a first-class policy value: pass it around, test it in isolation, chain with andThen for audit logging, and swap it when the policy changes.
Remember one sentence: BiFunction
---