枚举应实现策略接口而非仅作常量,通过构造器注入行为逻辑,提供fromcode安全转换,避免动态扩展场景,并配置@jsonvalue/@jsoncreator保障序列化兼容性。

用枚举实现策略接口,而不是只存常量
Java 枚举天然支持字段、构造器和方法,直接让枚举实现策略接口,比用 if-else 或 switch 分发更安全、更易维护。别把枚举当“高级常量”,它本就是轻量级类。
常见错误是定义空枚举再额外写一堆 StrategyFactory 类或静态映射表,反而增加间接层和出错点。
- 每个枚举项必须在声明时传入具体行为逻辑(通常通过构造器接收 lambda 或实现类)
- 策略接口方法必须是抽象的,不能默认实现——否则枚举项可能意外继承而非重写
- 避免在枚举构造器里调用尚未初始化的其他枚举项,会触发
java.lang.ExceptionInInitializerError
public interface DiscountStrategy {
BigDecimal apply(BigDecimal original);
}
public enum DiscountType implements DiscountStrategy {
EARLY_BIRD(price -> price.multiply(BigDecimal.valueOf(0.9))),
MEMBER(price -> price.multiply(BigDecimal.valueOf(0.85))),
CLEARANCE(price -> price.multiply(BigDecimal.valueOf(0.5)));
private final Function<BigDecimal, BigDecimal> calculator;
DiscountType(Function<BigDecimal, BigDecimal> calculator) {
this.calculator = calculator;
}
@Override
public BigDecimal apply(BigDecimal original) {
return calculator.apply(original);
}
}
从字符串/数据库值安全获取枚举实例
外部输入(如 HTTP 参数、JSON 字段、DB 字符串)转枚举最容易抛 IllegalArgumentException,尤其遇到拼写错误或新增类型未同步时。
别用 DiscountType.valueOf("early_bird") —— 它严格匹配枚举名,且不处理大小写或下划线转换。
立即学习“Java免费学习笔记(深入)”;
- 在枚举中加一个静态
fromCode(String code)方法,内部用Stream查找匹配code字段(不是枚举名) - 给每个枚举项加一个
code字段,比如"early_bird",和业务系统对齐 - 查不到时返回
null或抛自定义异常,不要静默 fallback 到第一个枚举项
public enum DiscountType implements DiscountStrategy {
EARLY_BIRD("early_bird", price -> price.multiply(BigDecimal.valueOf(0.9))),
MEMBER("member", price -> price.multiply(BigDecimal.valueOf(0.85)));
private final String code;
private final Function<BigDecimal, BigDecimal> calculator;
DiscountType(String code, Function<BigDecimal, BigDecimal> calculator) {
this.code = code;
this.calculator = calculator;
}
public static DiscountType fromCode(String code) {
return Arrays.stream(values())
.filter(v -> v.code.equalsIgnoreCase(code))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown discount code: " + code));
}
}
枚举策略无法动态扩展,别硬套在需要热加载的场景
枚举类在 JVM 加载后就固定了,没法运行时注册新策略。如果业务要求“运营后台配置新折扣类型并立即生效”,用枚举就是错的路。
这时候该用 Spring 的 @Service + 接口实现类 + ApplicationContext.getBeansOfType() 动态发现,或者用策略注册表(Map<string strategy></string>)手动管理。
- 枚举适合策略种类稳定、变更频率低(如支付方式:WECHAT_PAY、ALIPAY、CARD)
- 若策略逻辑本身要频繁改(比如折扣公式下周就变),把计算逻辑抽到外部服务或配置中心,枚举只负责路由
- 测试时别 mock 枚举——它不能被代理,要用真实实例或提取接口隔离
注意序列化兼容性:Jackson 默认按 name 序列化,但你可能想要 code
前端传 {"type": "early_bird"},后端反序列化失败,是因为 Jackson 默认用 name()(即 EARLY_BIRD)做 JSON key,而不是你定义的 code 字段。
- 加
@JsonValue在 getter 上,让 Jackson 序列化时输出code - 加
@JsonCreator静态工厂方法,让反序列化走fromCode() - Spring Boot 2.6+ 默认禁用
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,但建议显式打开,避免脏数据静默吞掉
public enum DiscountType {
EARLY_BIRD("early_bird", ...);
private final String code;
DiscountType(String code, ...) { this.code = code; }
@JsonValue
public String getCode() { return code; }
@JsonCreator
public static DiscountType fromCode(String code) { ... }
}
枚举策略看着简洁,但一旦涉及外部输入、序列化、动态性,边界就立刻清晰起来——它不是万能胶,而是有明确适用边界的工具。用之前先问一句:这个“策略集合”未来半年会不会新增?要不要 runtime 可配?答案决定你该不该打开这个枚举文件。










