当前位置:首页 > 文章列表 > 文章 > java教程 > Java record compact constructor 的校验位置

Java record compact constructor 的校验位置

来源:17golang原创 2026-11-01 06:24:54 0浏览 收藏

Java record 的 compact constructor(紧凑构造器)适合放“对象一旦创建就必须满足”的参数校验和规范化。构造器体内直接使用组件名,例如 code、copies;不要给 this.code 赋值,也不要通过访问器读取尚未完成初始化的对象来校验。构造器体正常结束后,编译器才把对应参数的最终值写入组件字段。

我更倾向于把这种校验当成数据对象的入口契约:接口、导入任务和内部调用都应该遵守,而库存是否充足、用户有没有权限等依赖外部状态的判断交给服务层。下面以一个虚构的印制任务参数对象说明这种取舍,示例以 Java 21 为语义基线,没有声称在本机执行或获得线上效果。

Java 官方规范地址:https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.10.4.2

创建入口增多时,校验为什么不能只留在服务层

假设一个印制任务需要任务代码、份数和标签。最初只有一个 HTTP 入口,开发者可能在控制器里检查份数是否大于零。后来增加批量导入与定时任务,如果它们直接创建相同的 record,入口校验就可能分散成三份。这里的维护压力来自创建路径数量,不能简单归结为请求量。

我会先写下两类规则。第一类只看传入数据,例如代码不能为空、份数必须为正、标签不能含空项;第二类必须查别的系统,例如纸张是否有库存、操作员是否有权限。把第一类约束集中到构造器,让第二类留在业务调用处,职责会清楚得多。这是本文示例的设计建议,并不是 record 自动完成的业务能力。

这种改造也有代价:原来可以暂存非法状态的调用,现在会在构造时失败。若导入页面需要一次展示几十条错误,我会先保留独立的原始输入模型,收集错误后才创建有效 record,而不是让领域对象承载“有一半字段还没修好”的状态。

紧凑构造器里检查参数并规范化

紧凑形式省略的是参数列表和字段赋值样板,参数本身仍存在。JLS 8.10.4.2 规定,构造器体里的组件简单名称指向隐式形参;体内不能向组件字段赋值,不能写 return,也不能显式调用另一个构造器。正常结束之后的隐式字段初始化使用的是参数当时的值。因此,规范化要重新赋给参数。

import java.util.Objects;

public record PrintSpec(String code, int copies) {
    public PrintSpec {
        // 必需值先拒绝空引用,再按本对象约定去掉首尾空白。
        code = Objects.requireNonNull(code, "任务代码不能为空").strip();
        if (code.isEmpty()) {
            throw new IllegalArgumentException("任务代码不能只含空白");
        }
        // 数量约束与其他创建入口共享,不读取尚未赋值的字段。
        if (copies 

这里先判空,再去空白,最后判断空字符串,是本例针对代码输入的策略。若协议要求保留首尾空格,就应该改为拒绝或原样保留;规范化不是通用修复器。也不要只调用 code.strip() 却丢掉返回值:这个例子需要让参数绑定到规范化后的字符串。

紧凑构造器参数、局部规则与组件字段的静态关系
图1:参数与字段结构说明图。参数组是体内校验和规范化的对象,字段组表示正常完成后的存储位置;连线只表示对应关系,不表示运行截图或执行流程。

我特别会避开 check(this) 这种写法。若 check 内部通过访问器读取组件,它看的不是当前参数值。应把参数传给静态辅助方法,例如传入 code 和 copies;辅助方法也不需要持有正在构造的对象。完整规范构造器可以显式赋字段,但它和紧凑构造器不能同时作为同一个 record 的规范构造器。

集合组件的复制与校验放在同一个入口

字符串与整数容易处理,集合更值得明确所有权。若只把外部 List 引用放进 final 字段,调用方仍可能修改原列表。我倾向于在紧凑构造器里获得不可修改列表,再检查本对象需要的元素规则;下面是另一个独立的 record 示例,可保存为 PrintBatch.java。

import java.util.List;
import java.util.Objects;

public record PrintBatch(List labels, int copies) {
    public PrintBatch {
        // copyOf 拒绝空元素,并使外部列表后续改动不影响这里保存的列表内容。
        labels = List.copyOf(Objects.requireNonNull(labels, "标签列表不能为空"));
        if (labels.isEmpty()) {
            throw new IllegalArgumentException("至少提供一个标签");
        }
        for (String label : labels) {
            // 本例拒绝空白标签,不擅自改变标签原文。
            if (label.isBlank()) {
                throw new IllegalArgumentException("标签不能只含空白");
            }
        }
        if (copies 

List.copyOf 的契约是不可修改列表,不保证每次创建新对象。输入为 null 或包含 null 元素时会失败;它保留元素引用,不提供深复制。示例使用 String,若换成可变的标签对象,还要为元素定义单独的快照策略。复制的遍历与存储成本也要纳入大批量输入的资源预算,不能把它宣传成没有成本的防护。

为了让错误提示稳定,可以把参数规则整理成表。表中异常来自这两份示例的具体写法,不代表所有业务必须使用相同异常类别。

输入情况处理位置示例的预期表现
代码为空引用参数判空NullPointerException
代码只有空白规范化后检查IllegalArgumentException
份数为零或负数数值规则IllegalArgumentException
列表含空引用元素List.copyOfNullPointerException
标签只有空白元素规则IllegalArgumentException

哪些逻辑应该留在 record 外部

把规则集中到构造器,不等于把服务层搬进去。对于同样的 code 和 copies,如果校验结果取决于网络是否可用、库存此刻是否变化,这个对象就有了额外的外部依赖。我的取舍是让 record 负责自身局部不变量,让服务负责授权、库存与事务判断,并由请求边界把异常转换为适当的用户提示。

原始输入、record 局部契约与业务服务职责的静态关系
图2:校验职责结构说明图。原始输入保留表单问题,record 约束局部数据,业务服务承接权限与库存;组间连线表示依赖或职责关联,并不表示库存检查具备事务保证。

比如“份数必须大于零”可以由构造器保证,“目前库存足够印制”还需要库存服务的并发控制。即便曾经查过库存,也不能据此保证后续提交一定成功。不要在构造器里预留库存、发送消息或写订单,这些副作用应由业务操作明确承担失败处理。

同样,构造器适合尽早拒绝一份不可接受的数据,却不天然提供“全部错误一起返回”的界面体验。原始输入模型可以保存用户输入与错误集合,服务完成必要的外部判断后再创建或使用 record。这种分工比在紧凑构造器里累积 UI 状态更容易说明。

改造后按输入契约观察结果

本文没有上线数据。对这样的改造,我关注的是调用方行为是否符合预期:所有常规创建路径是否使用同一构造入口,非法输入能否被边界层解释,原始列表变化是否影响已创建对象,以及新复制成本是否适合输入规模。

读者在自己的项目中可以安排几组对照输入:带首尾空白的任务代码、只含空白的代码、零份数、空标签列表、含 null 的列表,以及创建后再修改原始列表。对照目标是上文的契约,而不是比较某段固定控制台文本。并发修改输入集合需要调用方先提供稳定快照,构造器不是通用的并发快照设施。

我会先迁移约束少、字段含义明确的值对象,再处理需要兼容旧数据的入口。若历史数据允许零份数,不应悄悄让新构造器吞掉错误或自动改成一份;应明确迁移策略、旧数据解释和错误提示。对于字段需要跨多个交互阶段逐步填写的编辑模型,普通可变模型可能更符合任务。

有关构造器校验的三个问题

把校验写进访问器可以吗?

如果目标是拒绝非法对象,应在创建边界完成,而不是等每次读取才发现。访问器可以有明确的自定义行为,但不应承担补齐未完成构造的职责。

可以添加更短参数列表的构造器吗?

可以提供非规范构造器,但仍需按语言规则委托到其他构造器,最终抵达规范构造入口。默认参数也必须满足不变量,不能用重载绕开检查。这里不展开不同版本的一般构造器语法变化。

仅添加校验注解就会自动检查吗?

不要把注解当作这段 Java 代码的自动执行逻辑。若使用框架验证,必须了解对应框架何时触发验证、覆盖哪些创建路径。对于这里的纯 Java 示例,实际执行的是构造器中的显式检查。

进一步阅读:JLS 紧凑规范构造器、Oracle record 类指南、List.copyOf 官方契约。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java switch pattern matching 的 null 分支处理Java switch pattern matching 的 null 分支处理
上一篇
Java switch pattern matching 的 null 分支处理
Java Formatter 位置参数怎样复用同一个值
下一篇
Java Formatter 位置参数怎样复用同一个值
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    821次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    812次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    904次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    874次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    696次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码