Skip to content

Record 支持继承吗?如何用紧凑构造函数做参数校验? ​

  • 标签:Java、新特性

🏛 业务架构师 ​

两个问题分开答:

1. Record 支持继承吗——不支持:Record 隐式 final(不能被继承),也不能继承其他类(隐式继承 java.lang.Record),只能实现接口。这是刻意设计:Record 的 equals/hashCode/解构基于"组件列表",若允许继承,值语义就会破坏(子类字段不在父类比较逻辑里,等价性混乱)。业务上的"复用"需求用组合解决(Record 字段引用共享的 Record)或接口抽象行为。

2. 紧凑构造函数做参数校验:

java
public record Money(BigDecimal amount, Currency currency) {
    public Money {                       // 紧凑构造:无参数列表
        Objects.requireNonNull(currency, "币种不能为空");
        if (amount == null || amount.signum() < 0) {
            throw new IllegalArgumentException("金额必须为非负数");
        }
        amount = amount.setScale(2, RoundingMode.HALF_UP); // 可规范化字段
    }
}

紧凑构造在字段赋值前执行——校验失败对象根本不会诞生。业务价值:把"非法状态不可表示"(make illegal states unrepresentable)落到实处——钱、时间区间、身份证号这类业务值对象,构造即校验,非法数据在系统边界(接口入参解析、消息消费)就被拦截,而不是带病流转到核心逻辑才爆 NPE。相比散落在 Service 里的 if 校验,值对象校验是一次编写、处处防护的资产,配合密封类与模式匹配,构成 Java 现代业务建模的三件套。

🏗 系统架构师 ​

继承:Record 不能 extends 任何类(隐式继承 Record),也不能被继承(隐式 final)——但可以实现接口。这是设计取舍:Record 的价值就在"状态即身份"的纯粹性,继承会破坏 equals 语义的可比性(不同子类比较的歧义)。多态需求用 sealed interface + Record 实现的组合表达。

紧凑构造函数(Compact Constructor)做参数校验——没有参数列表(与 record header 一致),在字段赋值前执行,是官方指定的校验位置:

java
public record Money(BigDecimal amount, Currency currency) {
    public Money {                       // 紧凑构造:无括号参数
        Objects.requireNonNull(currency, "currency 不能为空");
        if (amount == null || amount.signum() < 0)
            throw new IllegalArgumentException("amount 必须非负: " + amount);
        amount = amount.setScale(2, RoundingMode.HALF_UP);  // 可规范化参数
    }                                    // 结束时自动 this.amount = amount
}

public record Range(int low, int high) {
    public Range {
        if (low > high) throw new IllegalArgumentException(low + " > " + high);
    }
}

架构价值:把不变式(invariant)固化在类型里——构造即合法,杜绝"半初始化对象"在系统中流转。相比 JavaBean 的 setter 模式(对象生命周期内状态可漂移、校验散落 service 层),Record + 紧凑构造让"非法状态不可表示"(make illegal states unrepresentable)——这是 DDD 值对象的标准实现,也是防御式编程的最低成本形态。我团队的规范:跨边界的 DTO 一律带紧凑构造校验,让边界错误在反序列化处爆炸而不是在业务深处。

💻 全栈开发专家 ​

继承:不支持。Record 隐式 final 且已隐式继承 java.lang.Record,Java 单继承下不能再 extends 别的类;但可以实现接口——接口抽象行为的组合是推荐的扩展方式:

java
public interface Payable { BigDecimal total(); }
public record Order(String id, List<OrderItem> items) implements Payable {
    public BigDecimal total() { return items.stream()...; }
}

Record 之间共享代码用「组件组合」而不是继承(record 里嵌 record)。

紧凑构造函数(Compact Constructor)做参数校验——没有参数列表(就是类名),可直接重新赋值规范化参数:

java
public record User(String name, String email, int age) {
    public User {                                   // 紧凑形态
        name = name == null ? "anonymous" : name.trim();
        if (email == null || !email.contains("@"))
            throw new IllegalArgumentException("非法邮箱: " + email);
        if (age < 0 || age > 150)
            throw new IllegalArgumentException("age 越界");
    }
    // 需要完整形态时(如防御拷贝):
    // public User(String name, String email, int age) { this(...); }
}

工程要点:

  • 防御性拷贝:可变组件(List)要么在紧凑构造器里 List.copyOf(items)(顺带判 null),否则记录的「不可变」被可变字段破了防;
  • 静态工厂放 record 体里很自然:static User of(String line) { ... };
  • Jackson 反序列化走 canonical constructor,校验逻辑自动生效——API 入口的数据合法性前移到反序列化点,这是 Record 校验的最大工程价值:脏数据进不了系统。