当前位置: 首页 > news >正文

函数式编程:用BiFunction消除多类型分支的代码重复

下面拿一个我在生产环境用过的一个库存变更的场景作为例子,演示一下如何使用JDK自带的BiFunction。也是函数编程的一种使用。

问题场景

一个库存日报表模块,支持三种变更类型:期初库存、在途库存、入库库存。每种类型对应DailyInventoryRecord上一个不同的更新方法:updateOpeningInventoryupdateInTransitInventoryupdateReceivedInventory

业务逻辑分两条路径:新增一条库存记录,或者更新已有记录。两条路径里都要根据类型判断该调哪个方法。

先看新增路径:

if (TYPE_OPENING.equals(type)) { record.updateOpeningInventory(param); } else if (TYPE_IN_TRANSIT.equals(type)) { record.updateInTransitInventory(param); } else if (TYPE_RECEIVED.equals(type)) { record.updateReceivedInventory(param); }

更新路径里,一模一样的if-else再来一遍,只是record对象换了个变量名。

问题出在哪?两个路径里的if-else结构完全一样,只是调用位置不同。如果后面要加一种「出库库存」类型,新增路径要改,更新路径也要改。改两处本身不费事,但容易漏。

能不能把「调哪个方法」这个决策提前做一次,后面的两条路径直接拿着结果去调用?

解法:switch表达式 + BiFunction + 方法引用

BiFunction是JDK 8引入的函数式接口,在java.util.function包下,接收两个参数,返回一个结果。用在这个场景刚好合适:更新方法需要接收记录对象和变更参数两个入参,返回更新后的记录对象。

核心就这几行:

BiFunction<DailyInventoryRecord, InventoryChangeParam, DailyInventoryRecord> fn = switch (type) { case TYPE_OPENING -> DailyInventoryRecord::updateOpeningInventory; case TYPE_IN_TRANSIT -> DailyInventoryRecord::updateInTransitInventory; case TYPE_RECEIVED -> DailyInventoryRecord::updateReceivedInventory; };

这里用了Java 14引入的switch表达式配合方法引用。DailyInventoryRecord::updateOpeningInventory是一个未绑定的方法引用,编译器会自动把调用对象本身当作第一个参数。所以它的实际签名是(DailyInventoryRecord, InventoryChangeParam) -> DailyInventoryRecord,和BiFunction的泛型完全匹配。

有了这个fn,新增和更新两条路径都不需要再写if-else了。

新增路径里,buildNewRecords方法接收fn作为参数,内部统一调用:

// 构建新增记录 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(); }

更新路径更直接,循环里调一下:

// 更新已有记录 for (DailyInventoryRecord record : existingRecords) { fn.apply(record, paramMap.get(record.getMaterialCode())); }

两条路径的代码里没有任何类型判断,都是直接fn.apply()。if-else被集中到了fn赋值的那一个地方。

这种写法用起来很顺,但它能成立有一个硬性前提。

前提条件

能被同一个BiFunction统一的方法,入参个数、参数类型、返回值类型必须完全一致。

拿这个例子来说,三个更新方法都是接收一个InventoryChangeParam,返回DailyInventoryRecord自身。签名一致,才能用方法引用统一赋值。如果其中某个方法多了一个参数,或者返回类型不同,BiFunction的泛型就约束不住了,编译直接报错。

所以这不是一个万能的模式。方法签名不一致的时候,别硬往上套,老老实实用if-else反而更清晰。

回到整体来看,改前和改后的差异到底在哪?

改前改后对比

维度改前(if-else分散)改后(BiFunction统一)
类型判断代码两条路径各写一遍if-elseswitch只写一次
新增类型时要改的地方两个路径各改一处,共2处switch里加一行,共1处
漏改风险高,改一处容易忘改另一处低,只有一个入口
使用路径的代码每个路径内部都有分支逻辑统一fn.apply(),无分支

判断要不要用这个模式的标准很具体:看那几个分支里调用的方法签名是否一致。入参个数、参数类型、返回值都相同,用函数式接口统一就很自然。签名不一致,强统一反而要写额外的适配代码,不如保持if-else。

小结

Java的函数式接口在实际项目里最有用的地方,不是替代所有条件分支,而是处理「结构相同、方法不同」这一类重复代码。BiFunctionConsumerFunction这些接口在JDK里已经存在了很多年,问题不在于知不知道它们,而在于遇到合适场景的时候能不能想起来用。

判断标准只有一个:分支里各方法的签名能不能统一。能统一,用函数式接口收拢到一个变量里,调用方只管apply(),不用关心调的是哪个具体方法。不能统一,就保持if-else,别勉强。这种模式算不上什么高级技巧,用多了就成了习惯。

http://www.jsqmd.com/news/1106372/

相关文章:

  • Java毕设选题推荐:基于 Java 的学术资料智能检索管理系统的设计与实现 基于 Java 的文献资源分类统计管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 海量 MCP 工具场景下的上下文隔离选择方案
  • 性价比高的降英文AI工具推荐工具
  • 【NWFSP问题】基于matlab麝牛算法MO求解零等待流水车间调度问题NWFSP【含Matlab源码 15685期】
  • WAIC 2026前瞻:AI办公不缺聊天机器人,缺的是可信会议智能体
  • 内网渗透测试实战指南:从信息收集到域控攻防的完整攻击链
  • 节点】[SmoothStep节点]原理解析与实际应用
  • ZXing:一个扫描条码的基础库
  • Dockery:一个容器跑起来,就是你的私有 Docker Registry
  • GitHub 53K Star 爆款:不用 JS 逆向,7 大平台数据一把抓
  • 2026 跨境云网融合服务商榜单:海外企业组网与安全运维推荐
  • 企业微信二次开发中的定期对账机制
  • 墨香情手游官方下载:重拾纯粹武侠情怀开启全新快意恩仇江湖征途
  • 2026年AIGC检测怎么过?5大检测平台对比+AI痕迹降低实战指南
  • ICM-42688-P与PIC18F4553在机器人控制与工业监测中的应用
  • 类比StandardServer, 抓住StandardService整体类依赖结构来理解
  • Better BibTeX架构解析:为LaTeX用户打造的企业级文献管理解决方案
  • 【节点】[Clamp节点]原理解析与实际应用
  • Kubernetes 核心机制与运维实践知识精要
  • 别折腾了!3步教你用标准 API 调通企业微信外部群机器人
  • Python 最大冤案:你以为 `await` 在“死等”?它其实在
  • 如何轻松地从 iPhone 备份恢复 iPad?
  • 任务计划程序不显示后边的信息
  • 墨香情手游官方下载:均衡稳定经济生态适配养老休闲打金玩家群体
  • 张鹏翔在AI营销实战方法论沙龙上详解智能体如何助力企业长效流量增长
  • Apache SeaTunnel 搞定瀚高数据库读写一把过
  • SPI EEPROM与MCU高速数据检索方案解析
  • 如何把报告错误消灭在出稿前?AI报告审核结合IACheck实现前置校验
  • 好用还专业!盘点2026年最强的的降AI率软件
  • 别再建一个无人问津的知识库:用AI原生平台打造活文档系统