PAYATHON 2026

Java 中应使用 enum 还是 int 表示付款频率?

支付老李

结论

这种场景更适合使用 enum。

付款频率不是任意整数,而是一组有限且含义明确的选项:月付、季付、半年付和年付。enum 可以从类型层面限定合法值,避免将 3、5、10 等无效或含义不清的数字传入业务代码,也能为每种频率保存“每年付款次数”等属性。

推荐实现

public enum PaymentFrequency {
    MONTHLY(12),
    QUARTERLY(4),
    SEMIANNUALLY(2),
    ANNUALLY(1);

    private final int paymentsPerYear;

    PaymentFrequency(int paymentsPerYear) {
        this.paymentsPerYear = paymentsPerYear;
    }

    public int getPaymentsPerYear() {
        return paymentsPerYear;
    }
}

在 Insurance 中直接使用这个类型:

import java.util.Objects;

public abstract class Insurance {
    private int yearlyPremium;
    private PaymentFrequency paymentFrequency;

    protected Insurance(
            int yearlyPremium,
            PaymentFrequency paymentFrequency) {
        this.yearlyPremium = yearlyPremium;
        this.paymentFrequency =
                Objects.requireNonNull(paymentFrequency);
    }

    public int getPaymentAmount() {
        return yearlyPremium
                / paymentFrequency.getPaymentsPerYear();
    }

    public PaymentFrequency getPaymentFrequency() {
        return paymentFrequency;
    }
}

如果保费涉及货币和小数,就不应依赖整数除法。通常应使用 BigDecimal,并明确指定舍入规则:

import java.math.BigDecimal;
import java.math.RoundingMode;

public BigDecimal getPaymentAmount() {
    return yearlyPremium.divide(
            BigDecimal.valueOf(paymentFrequency.getPaymentsPerYear()),
            2,
            RoundingMode.HALF_UP
    );
}

这段代码假设 yearlyPremium 已改为 BigDecimal。实际项目中,精度和舍入方式应根据币种的最小单位及业务规则确定。

为什么不建议直接使用 int

使用 int 时,方法签名无法说明参数的业务含义:

insurance.setPaymentFrequency(4);

单看这段代码,无法确定 4 是指每年支付四次、每四个月支付一次,还是某个内部编号。由于调用者可以传入任意整数,每个入口都可能需要进行验证:

private static boolean isValidFrequency(int paymentsPerYear) {
    return paymentsPerYear >= 1
            && paymentsPerYear <= 12
            && 12 % paymentsPerYear == 0;
}

改用枚举后,调用方式会清楚得多:

insurance.setPaymentFrequency(PaymentFrequency.QUARTERLY);

调用者无法传入枚举定义之外的付款频率,类型系统也能承担一部分校验工作,后续重构会更安全。

另外,12 % n == 0 只能判断数学上是否合法,不能完整表达业务规则。例如,它会允许 n == 3 和 n == 6,但当前枚举并未定义每年三次或每年六次付款。对于这种由业务预先确定的固定集合,枚举表达得更准确。

界面显示名称的处理

使用 PaymentFrequency.values() 生成下拉选项是合理的:

for (PaymentFrequency frequency : PaymentFrequency.values()) {
    System.out.println(
            frequency.name() + ": "
                    + frequency.getPaymentsPerYear()
    );
}

不过,"Monthly"、"Quarterly" 这类面向用户的文本不宜直接作为核心领域数据。显示文本可能需要翻译,也可能由产品配置决定。可以在枚举中保存稳定的消息键:

public enum PaymentFrequency {
    MONTHLY("payment.frequency.monthly", 12),
    QUARTERLY("payment.frequency.quarterly", 4),
    SEMIANNUALLY("payment.frequency.semiannually", 2),
    ANNUALLY("payment.frequency.annually", 1);

    private final String messageKey;
    private final int paymentsPerYear;

    PaymentFrequency(String messageKey, int paymentsPerYear) {
        this.messageKey = messageKey;
        this.paymentsPerYear = paymentsPerYear;
    }

    public String getMessageKey() {
        return messageKey;
    }

    public int getPaymentsPerYear() {
        return paymentsPerYear;
    }
}

界面层再通过 messageKey 从资源文件中读取本地化文本。如果应用确定只有一种语言,也可以直接保存显示名称,只是以后扩展会稍受限制。

从数字转换为枚举

数据库、配置文件或外部接口可能仍以每年付款次数表示频率。可以将转换和校验集中到枚举中:

import java.util.Arrays;

public enum PaymentFrequency {
    MONTHLY(12),
    QUARTERLY(4),
    SEMIANNUALLY(2),
    ANNUALLY(1);

    private final int paymentsPerYear;

    PaymentFrequency(int paymentsPerYear) {
        this.paymentsPerYear = paymentsPerYear;
    }

    public int getPaymentsPerYear() {
        return paymentsPerYear;
    }

    public static PaymentFrequency fromPaymentsPerYear(int value) {
        return Arrays.stream(values())
                .filter(frequency ->
                        frequency.paymentsPerYear == value)
                .findFirst()
                .orElseThrow(() ->
                        new IllegalArgumentException(
                                "Unsupported payment frequency: " + value
                        ));
    }
}

这样只需在系统边界处处理一次非法数字,领域模型内部始终使用合法的 PaymentFrequency。

注意事项

  • 枚举字段通常应声明为 private final,防止枚举常量创建后其属性被修改。
  • 不要使用 ordinal() 保存数据库值。调整枚举顺序会改变序号,应保存稳定名称或显式代码。
  • values() 按声明顺序返回常量。如果界面顺序与业务定义顺序不同,应由界面层单独排序。
  • 如果将来允许任意周期、用户自定义周期或“每 45 天一次”这类复杂日程,固定枚举可能无法满足需求,此时应改用专门的值对象或计划模型。
  • 新增枚举常量后,它虽然会自动出现在动态下拉框中,但数据库、序列化、报表和 switch 分支仍需同步检查。

只要允许的付款频率仍是由业务预先定义的有限集合,使用 enum 就不是过度设计,而是比裸 int 更准确的领域建模。

备注:内容仅供参考。