分享些 Function 和 枚举的经典使用案例
在 Java 项目里,枚举常被用来表示状态、类型或操作。但大多数团队对枚举的使用停留在“写常量、带点属性”的阶段,比如:
public enum BizType {ORDER, INVOICE, PAYMENT
}
这种枚举就是个“标签”,本身不具备行为。要根据枚举执行不同逻辑时,往往写成一堆 switch-case
或 if-else
。
而我后来在实践中发现:把 Function 绑定到 枚举 上,可以让逻辑和数据天然耦合,形成一种轻量的“枚举策略模式”。
1. 代替 switch-case
的支付示例
最典型的场景是支付业务。以前我们是这样写的:
if (payType == ALIPAY) {return doAliPay(request);
} else if (payType == WECHAT) {return doWechatPay(request);
} else if (payType == BANK) {return doBankPay(request);
}
重构后,用枚举直接绑定处理函数:
public enum PayType {ALIPAY(req -> PayService.aliPay(req)),WECHAT(req -> PayService.wechatPay(req)),BANK(req -> PayService.bankPay(req));private final Function<PayRequest, PayResult> function;PayType(Function<PayRequest, PayResult> function) {this.function = function;}public PayResult process(PayRequest req) {return function.apply(req);}
}
调用时只需:
PayResult result = payType.process(request);
好处:
- 新增支付方式时,只改枚举本身,不用动
switch-case
。 - 枚举项 自带行为,和“数据”天然绑定。
- 枚举 + Function 的写法,比传统策略模式少了冗余类文件。
2. 枚举做“状态机”的小技巧
在一个开票系统里,我们有多个发票状态:INIT
、SUBMITTED
、SUCCESS
、FAILED
。不同状态下,系统要执行不同的动作。
传统写法是一个大 switch
:
switch (status) {case INIT: doInit(); break;case SUBMITTED: doSubmitted(); break;case SUCCESS: doSuccess(); break;case FAILED: doFailed(); break;
}
重构后:
public enum InvoiceStatus {INIT(ctx -> InvoiceHandler.init(ctx)),SUBMITTED(ctx -> InvoiceHandler.submitted(ctx)),SUCCESS(ctx -> InvoiceHandler.success(ctx)),FAILED(ctx -> InvoiceHandler.failed(ctx));private final Function<InvoiceContext, Void> function;InvoiceStatus(Function<InvoiceContext, Void> function) {this.function = function;}public void handle(InvoiceContext ctx) {function.apply(ctx);}
}
调用:
invoiceStatus.handle(context);
这里枚举天然就是“状态机的节点”,Function 直接绑定行为,让代码逻辑自解释。
3. 枚举做计算器:更贴近业务的表达
在财务模块里,我们需要根据不同的运算符执行计算,比如:
if ("ADD".equals(op)) {return a.add(b);
} else if ("SUB".equals(op)) {return a.subtract(b);
} else if ("MUL".equals(op)) {return a.multiply(b);
}
枚举 + Function 的写法:
public enum Operator {ADD((a, b) -> a.add(b)),SUB((a, b) -> a.subtract(b)),MUL((a, b) -> a.multiply(b)),DIV((a, b) -> a.divide(b, RoundingMode.HALF_UP));private final BiFunction<BigDecimal, BigDecimal, BigDecimal> op;Operator(BiFunction<BigDecimal, BigDecimal, BigDecimal> op) {this.op = op;}public BigDecimal apply(BigDecimal a, BigDecimal b) {return op.apply(a, b);}
}
调用:
BigDecimal result = Operator.MUL.apply(new BigDecimal("12"), new BigDecimal("8"));
业务代码可读性直接提升:Operator.MUL.apply(12, 8)
,一眼就能看懂“乘法操作”。
4. 我的实践体会
- 适用场景:当枚举不仅表示“值”,还天然带有“处理逻辑”时,用 Function 封装行为非常自然。
- 比策略模式更轻:很多团队一上来就用策略模式,类会暴增;而枚举 + Function 足够轻巧,维护成本低。
- 注意职责边界:别把一堆复杂逻辑硬塞到枚举里,否则枚举变成“大怪物”。我的经验是:枚举负责路由,复杂逻辑交给外部 Service。
- 调试友好:当业务报错时,可以直接打印枚举名,日志天然带有“当前逻辑标签”,比硬编码的 if-else 更清晰。
结语
Function 和枚举的结合,是我在 Java 开发里最喜欢的一个“小技巧”。它既保留了枚举的 强约束性,又引入了函数式的 灵活性。
和传统的策略模式相比,它更轻、更直观,写出来的代码逻辑自带文档属性。很多时候,只要看到枚举定义,你就能立即知道系统有哪些行为,以及每个行为的处理方式。
推荐阅读文章
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
什么是 Cookie?简单介绍与使用方法
-
什么是 Session?如何应用?
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
如何理解应用 Java 多线程与并发编程?
-
把握Java泛型的艺术:协变、逆变与不可变性一网打尽
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
如何理解线程安全这个概念?
-
理解 Java 桥接方法
-
Spring 整合嵌入式 Tomcat 容器
-
Tomcat 如何加载 SpringMVC 组件
-
“在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”
-
“避免序列化灾难:掌握实现 Serializable 的真相!(二)”
-
如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)
-
解密 Redis:如何通过 IO 多路复用征服高并发挑战!
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
-
Java 中消除 If-else 技巧总结
-
线程池的核心参数配置(仅供参考)
-
【人工智能】聊聊Transformer,深度学习的一股清流(13)
-
Java 枚举的几个常用技巧,你可以试着用用
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
-
为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)