Compact Constructor 是 record 唯一能安全介入构造流程、执行入参校验的位置,校验逻辑必须写在 this() 调用之前,禁止赋值 final 字段,仅允许抛出 IllegalArgumentException 等运行时异常。

Compact Constructor 是 record 唯一能安全介入构造流程、执行入参校验的位置,其他任何地方(包括字段赋值后、方法里、工厂类中)都无法保证校验被强制触发。
Compact Constructor 必须显式定义且 this() 调用要放在最后
record 不会自动生成你写的 compact constructor;一旦你声明了一个与组件签名一致的 public 构造器,编译器就放弃生成默认版本,转而要求你手动调用 this() —— 这个调用必须出现在构造器末尾,且之前不能有任何对 this.xxx 的赋值(final 字段不可写)。
常见错误是把 this() 写在开头,或漏掉它,导致编译失败;也有人误以为可以像普通类那样在构造器里改字段,结果触发 error: cannot assign a value to final variable。
- 校验逻辑(如
Objects.requireNonNull(name)、requireNonBlank(email))必须写在this()之前 - 允许修改参数值(如
email = email.trim()),这个新值会被自动赋给对应 final 字段 - 禁止出现
super()、this.xxx = ...、return;提前退出(除非抛异常)
校验失败时只能抛 RuntimeException
record 的 compact constructor 签名不支持 throws 子句,所以无法声明 checked exception。若校验失败,必须用 IllegalArgumentException、IllegalStateException 或其子类——这是唯一能让 new 表达式立即中断、不产生有效实例的方式。
如果错用 Exception 或自定义 checked 异常,编译直接报错:constructor cannot throw checked exception。
-
IllegalArgumentException最常用,语义清晰(参数本身不合法) - 避免用
NullPointerException:它不该由业务逻辑主动抛,且掩盖了真实校验意图 - 不要 try-catch 吞掉异常:那会让校验形同虚设
Jackson 反序列化和 JPA 入库都会触发 compact constructor
从 Jackson 2.15.2 开始,启用 RecordModule 后,JSON 反序列化 record 实例会原样走 compact constructor 流程——也就是说,你写的校验逻辑对 HTTP 请求体、配置文件、MQ 消息等所有反序列化场景都生效。
JPA(如 Hibernate)在将查询结果映射为 record 时,也依赖反射调用 compact constructor(而非私有全参构造器),因此同样触发校验。
- 不需要额外包装或代理,
@JsonCreator或@ConstructorProperties都不是必需的 - 但要注意:某些老版本 Jackson(RecordModule 时,可能跳过 compact constructor,直接调私有构造器 → 校验失效
- 手动
new User(" ", -1)也会触发,验证最简单
别把校验逻辑和业务规则混在一起
compact constructor 的职责非常明确:守住“数据结构成立”的底线。比如非空、长度范围、邮箱格式、枚举合法性。它不是放业务规则的地方——例如“用户年龄必须大于最小入职年龄”“邮箱域名必须属于白名单”,这类规则应交给 service 层或 domain service 处理。
否则 record 会快速膨胀,变成一个带校验的贫血模型,既失去简洁性,又模糊了边界。
- 适合放这里的:
Objects.requireNonNull(name)、age >= 0 && age 、<code>email.matches("^.+@.+$") - 不适合放这里的:
isDomainAllowed(email)、userRepo.existsByEmail(email)、calculateTaxRate(country, income) - 如果校验逻辑开始需要外部依赖(DB、HTTP、Cache),说明它已经不属于 compact constructor 的范畴
真正容易被忽略的是:compact constructor 的校验只对“构造行为”有效。如果你通过反射绕过它(比如 Unsafe.allocateInstance()),或者用字节码工具跳过初始化,校验就完全失效——但这不是设计缺陷,而是 record 明确的契约:它只保障通过合法构造路径创建的实例符合约束。

















