Functional Programming: Using BiFunction to Eliminate Repetitive Multi‑Type Branches
The article shows how a Java 14 switch expression combined with a BiFunction and method references can replace duplicated if‑else branches in inventory‑change code, reducing maintenance effort and lowering the risk of missed updates when new types are added.
In a production inventory‑report module there are three change types—opening, in‑transit, and received—each with its own update method ( updateOpeningInventory, updateInTransitInventory, updateReceivedInventory) on DailyInventoryRecord. Both the "add new record" and the "update existing record" paths contain identical if‑else chains that select the proper method based on the type.
Problem
The duplicated conditional logic means that adding a new type requires changes in two separate places, increasing the chance of forgetting one of them.
Solution: switch expression + BiFunction + method reference
Java 8’s BiFunction (in java.util.function) accepts two arguments and returns a result. The update methods all have the signature
(DailyInventoryRecord, InventoryChangeParam) → DailyInventoryRecord, so they match the generic type
BiFunction<DailyInventoryRecord, InventoryChangeParam, DailyInventoryRecord>.
BiFunction<DailyInventoryRecord, InventoryChangeParam, DailyInventoryRecord> fn =
switch (type) {
case TYPE_OPENING -> DailyInventoryRecord::updateOpeningInventory;
case TYPE_IN_TRANSIT -> DailyInventoryRecord::updateInTransitInventory;
case TYPE_RECEIVED -> DailyInventoryRecord::updateReceivedInventory;
};The switch expression (Java 14) maps each enum value to an unbound method reference; the compiler treats the target object as the first argument, yielding the required BiFunction instance.
With fn defined, both the add‑new and update paths call fn.apply(...) directly, eliminating all if‑else statements.
// Build new records
private List<DailyInventoryRecord> buildNewRecords(
BiFunction<DailyInventoryRecord, InventoryChangeParam, DailyInventoryRecord> fn,
Set<String> newMaterialCodes,
Map<String, InventoryChangeParam> paramMap) {
return newMaterialCodes.stream().map(code -> {
DailyInventoryRecord record = new DailyInventoryRecord(code);
return fn.apply(record, paramMap.get(code));
}).toList();
} // Update existing records
for (DailyInventoryRecord record : existingRecords) {
fn.apply(record, paramMap.get(record.getMaterialCode()));
}All type‑checking logic is now centralized in the single switch that creates fn. The calling code no longer contains any branching.
Preconditions
The methods assigned to the BiFunction must have identical parameter counts, parameter types, and return type. If any method’s signature differs, the generic constraints of BiFunction will cause a compile‑time error, and the pattern should not be forced.
Before‑after comparison
Type‑checking code : duplicated if‑else in each path → single switch expression.
Places to modify when adding a type : two separate edits → one edit inside the switch.
Risk of missed updates : high (easy to forget one branch) → low (only one entry point).
Calling code : contains branching logic → unified call to fn.apply() with no branches.
The decision rule is simple: if the methods in the branches share the same signature, use a functional interface like BiFunction; otherwise keep the explicit if‑else.
Conclusion
Java’s built‑in functional interfaces are most useful when they can collapse "same structure, different method" repetitions. The key is to recognize when method signatures align; then a BiFunction (or Consumer, Function, etc.) can replace scattered conditionals, making the codebase cleaner and less error‑prone.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
