Java 16 正式引入的 record 是一种用于创建不可变数据载体的语法糖。它默认会生成一个包含所有组件参数的规范构造器,但开发者经常需要在这个构造过程中加入参数校验、归一化处理或者兼容老的调用方式。这时就会用到两种特殊写法:自定义构造器和紧凑构造器。它们虽然都能干预 record 的初始化,但在语法形态、参数传递以及使用约束上有着明显不同。

什么是 record 的规范构造器
当我们声明一个 record 时,例如 record Point(int x, int y),编译器会自动生成一个规范构造器,其签名与组件声明完全一致,并且会把参数直接赋值给对应的私有 final 字段。这个规范构造器是所有自定义构造逻辑的基础,任何其他的构造器最终都必须委托到它或者经由它完成字段初始化。
理解规范构造器很重要,因为无论是紧凑构造器还是自定义构造器,本质上都是在改变或增强这个默认过程。如果我们在 record 体内什么构造器都不写,编译器生成的等价代码大致如下:
public record Point(int x, int y) {
// 编译器自动生成的规范构造器
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
一旦我们手动提供了构造器,编译器就不会再生成这个默认的规范构造器,但我们写的构造器必须满足规范构造器的语义,即完成所有字段的赋值。
紧凑构造器的写法与特点
紧凑构造器是 Java 为 record 专门设计的语法,它没有参数列表,直接以构造器名加空括号体出现。编译器会自动将 record 的所有组件作为参数隐式传入,你可以在方法体里直接引用这些参数名进行校验或修正。
下面给一个使用紧凑构造器做参数校验的例子。我们规定坐标不能为负数,如果传入负值就抛出异常:
public record Point(int x, int y) {
// 紧凑构造器,没有参数列表
public Point {
if (x < 0) {
throw new IllegalArgumentException("x不能为负数");
}
if (y < 0) {
throw new IllegalArgumentException("y不能为负数");
}
}
}
在紧凑构造器里,你不能写 this.x = x; 这样的赋值语句,因为编译器会在紧凑构造器执行完毕后自动把参数赋给字段。如果你修改了参数变量(比如 x = x + 1;),那么最终赋值给字段的就是修改后的值,这常用于归一化。例如把传入的字符串去掉首尾空格:
public record User(String name) {
public User {
name = name == null ? "" : name.trim();
}
}
紧凑构造器的优势在于简洁且不易出错,你不需要重复写参数列表,也不会漏掉某个字段的赋值。它最适合做纯粹的校验和简单的参数整理工作。
自定义构造器的写法与特点
自定义构造器指的是显式声明了参数列表的构造器。它必须和某个合法签名匹配,并且在其方法体里的第一句必须是 this(...) 调用,也就是委托给同一个 record 里的其他构造器(通常是紧凑构造器或规范构造器),否则编译会报错。
当我们需要对入参做转换,或者想提供一个参数更少、参数类型不同的构造入口时,自定义构造器就很有用。比如我们想允许用字符串 "3,4" 来构造 Point:
public record Point(int x, int y) {
// 紧凑构造器做校验
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("坐标不能为负");
}
}
// 自定义构造器,解析字符串
public Point(String xy) {
this(Integer.parseInt(xy.split(",")[0]),
Integer.parseInt(xy.split(",")[1]));
}
}
注意上面自定义构造器首先调用了 this(int, int),而那个目标构造器实际就是由紧凑构造器增强过的规范构造器,因此字符串解析后仍然会走校验逻辑。如果你不写 this(...) 而尝试自己赋值字段,由于 record 字段是 final 且不属于你显式声明的成员,编译器会直接拒绝。
自定义构造器也可以不依赖紧凑构造器,直接委托给规范构造器并自己做处理,但那样容易和紧凑构造器逻辑重复。一般推荐把公共校验放在紧凑构造器,自定义构造器只做适配和转换然后委托出去。
两者核心区别对比
从语法和语义上看,紧凑构造器和自定义构造器主要区别集中在参数声明、赋值责任和适用场景上。下面的表格总结了关键差异:
| 对比维度 | 紧凑构造器 | 自定义构造器 |
|---|---|---|
| 参数列表 | 无,隐式使用全部组件参数 | 必须显式声明,可不同于组件签名 |
| 字段赋值 | 编译器自动完成,不能手动赋值 | 不可手动赋值,必须 this 委托 |
| 典型用途 | 参数校验、归一化 | 参数转换、兼容旧接口、重载入口 |
| 代码冗余度 | 低,无需重复参数 | 高,需写参数并调用 this |
可以看到,紧凑构造器更像是规范构造器的一个“增强块”,而自定义构造器则是完全重新定义的入口,只是受限于 record 的不可变规则必须通过委托来完成真正初始化。
在实际项目中,如果仅仅是为了防止非法值进入 record,优先使用紧凑构造器能让代码更短也更安全。只有当调用方传参形式和组件定义不一致时,才需要补充自定义构造器做桥接。
常见误用与注意点
有些开发者会在紧凑构造器里尝试写 this.x = x;,这会造成编译错误,因为紧凑构造器体内不允许显式给字段赋值。还有人会在自定义构造器里忘记写 this(...) 而直接写业务逻辑,结果编译器提示字段未初始化。记住:record 的所有构造路径最终都要归一到规范构造器。
另一个容易混淆的点是:如果在 record 里同时写了紧凑构造器和自定义构造器,自定义构造器调用的 this(...) 目标如果是紧凑构造器增强后的规范构造器,那么校验逻辑会被自然复用;但如果自定义构造器调用了另一个自定义构造器,就要确保那一层也最终连到了紧凑构造器,否则校验可能被绕过。
public record Range(int start, int end) {
public Range {
if (start > end) {
throw new IllegalArgumentException("start不能大于end");
}
}
// 正确:委托给带校验的规范构造器
public Range(int single) {
this(single, single);
}
}
以上写法中 Range(int) 通过 this(single, single) 走到了紧凑构造器,因此约束依然有效。这种组合方式既提供了便利重载,又保留了数据安全性。
小结
Java record 的紧凑构造器与自定义构造器并不是互斥的,它们分工明确。紧凑构造器用极简语法嵌入到规范构造流程中,适合做统一的参数检查与整理;自定义构造器则通过显式参数和强制委托,为我们提供灵活的创建方式。掌握二者的写法区别,能帮助你在使用 record 时既享受语法简洁,又不丢失对数据质量的把控。
Java_record紧凑构造器自定义构造器修改时间:2026-08-07 12:27:34