登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java record承载请求对象时的校验与默认值处理

来源:17golang原创

时间:2026-09-20 13:23:51 439浏览 收藏

把 HTTP 请求直接映射成 Java record,最容易踩的坑不是语法,而是把“缺省值”“非法值”和“调用方仍能修改集合”混在一起。我的处理方式是:紧凑构造器只负责建立对象不变量,能安全修复的空值在这里规范化,业务场景默认值交给工厂方法,集合字段统一做不可变拷贝。

官方资料:https://docs.oracle.com/en/java/javase/22/language/records.html

record 的规范构造器会在组件写入前接住参数,因此请求对象可以在一个边界内完成非空校验、默认值处理和格式收敛;但它不是深度不可变容器,嵌套的可变对象仍要单独处理。

先把三类输入分开

请求 DTO 通常同时包含三类字段。用户必须提供但不能修复的值,例如订单号为空,应直接抛出参数异常;可以接受缺省的值,例如页码为空,可以规范化为 1;集合和嵌套对象则要考虑所有权,不能因为 record 自带 final 组件就认为内容不会改变。

输入情况建议原因
必填字符串为空拒绝并给出字段名避免错误延迟到业务层
可选页码为 null规范化为稳定默认值调用方无需重复写默认逻辑
List 由外部传入List.copyOf切断后续 add/remove 影响

把校验和默认值放进紧凑构造器

紧凑构造器省略了参数列表和字段赋值,适合集中表达输入边界。下面的默认页码是接口约定,不是 Java 的“无参构造器默认值”;record 不会自动提供传统意义上的无参构造器。

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

public record SearchRequest(String keyword, Integer page, List fields) {
    public SearchRequest {
        // 必填字段无法修复,尽早拒绝并保留字段语义。
        keyword = Objects.requireNonNull(keyword, "keyword");
        if (keyword.isBlank()) {
            throw new IllegalArgumentException("keyword 不能为空");
        }
        // 可选页码统一落到接口约定的起始页,避免业务层重复判断。
        page = page == null ? 1 : page;
        if (page 
Java record 紧凑构造器中输入校验、默认值规范化和 record 组件赋值的结构说明图
图1:Java record 紧凑构造器的校验与规范化说明图,不是运行截图。

这里不要在构造器里悄悄吞掉所有异常。空白关键字和小于 1 的页码属于调用契约错误,应让错误响应能定位字段;只有“未传页码”这种明确的缺省状态才适合自动补值。

集合字段要做防御性拷贝

record 的组件字段是 private final,但如果组件类型是 ArrayList,final 只限制引用重新指向,不能限制列表内部变化。List.copyOf 会生成不可变视图语义的副本,并且拒绝 null 元素;如果请求还包含可变的嵌套对象,则需要在嵌套类型自己的构造边界继续复制。

var source = new java.util.ArrayList();
source.add("title");
var request = new SearchRequest("java record", 1, source);

// 构造后修改来源列表,不应改变 request.fields() 的内容。
source.add("price");
System.out.println(request.fields()); // 仍然只有 title

// 不要把可变集合直接返回给下游继续修改。
// request.fields().add("id") 会抛出 UnsupportedOperationException。
List.copyOf 隔离调用方列表与 Java record 不可变组件的结构图
图2:请求集合字段的不可变边界结构图,不是运行截图。

业务默认值交给工厂方法

构造器默认值解决的是字段级缺省,业务默认值往往还依赖租户、用户权限或接口版本,这时放在静态工厂方法更清楚。构造器保持“给定参数必须满足不变量”,工厂方法负责把场景信息整理成这些参数。

public static SearchRequest firstPage(String keyword) {
    // 场景方法表达“第一页查询”,构造器仍是唯一校验边界。
    return new SearchRequest(keyword, 1, List.of("title", "summary"));
}

如果框架通过 JSON 反序列化调用 canonical constructor,构造器校验仍会生效;但字段缺失究竟变成 null 还是被框架提前拒绝,取决于绑定配置。不要只在控制器参数注解里写规则,却让其他入口绕过同一套不变量。

落地前的检查清单

  • 必填字段是否用明确异常拒绝,而不是悄悄替换成空字符串?
  • 默认值是否属于字段契约,还是依赖租户和权限的业务决策?
  • 集合、数组、日期和嵌套对象是否仍然能从对象外部被修改?
  • 序列化、单元测试和错误响应是否都经过同一个构造边界?

record 让请求对象更短,但不会替你决定哪些输入可修复。把不变量集中到紧凑构造器,再用工厂方法表达场景默认值,通常能同时得到更清晰的错误边界和更可靠的不可变语义。

常见问题

record 能不能写无参构造器?

不能把传统无参构造器当作 record 的默认入口。需要默认值时,使用带明确语义的静态工厂方法,或声明完整的 canonical constructor。

List.copyOf 是深拷贝吗?

不是。它切断集合容器本身的修改关系,但集合元素若是可变对象,元素内部状态仍可能被改变;这类嵌套对象要继续设计自己的不可变边界。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>