写 Java 的都懂那种痛:定义一个简单的数据载体类——就装几个字段——结果要写一大坨样板。私有字段、全套 getter、构造方法、equals、hashCode、toString,一个只有三个字段的类能撑到七八十行。IDE 是能帮你生成,但生成完那一屏代码看着就累,字段一改还得重新生成。
Java 16 正式带来的 record 把这事彻底解决了。用了半年,我们项目里的 DTO、VO 这些纯数据类基本全换成了 record,样板代码砍掉一大半。今天讲讲它好在哪、什么时候该用什么时候别用。
![]()
传统 POJO 有多啰嗦
先看一个传统的不可变数据类要写多少东西。一个表示「点」的类,就 x、y 两个字段:
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) { // 构造方法
this.x = x;
this.y = y;
public int getX() { return x; } // getter
public int getY() { return y; }
@Override
public boolean equals(Object o) { // equals
if (this == o) return true;
if (!(o instanceof Point)) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
@Override
public int hashCode() { // hashCode
return Objects.hash(x, y);
@Override
public String toString() { // toString
return "Point{x=" + x + ", y=" + y + "}";
三十多行,真正的信息量就是「x 和 y 两个 int」,剩下全是机械样板。而且这些样板还有隐患:你给类加了个字段 z,得记着同步改构造方法、加 getter、改 equals、改 hashCode、改 toString,漏改一个就埋下 bug——比如 equals 忘了带上 z,两个 z 不同的对象会被判相等。
record:一行顶三十行
同样的类,用 record 写:
public record Point(int x, int y) {}
就这一行。record 关键字后面括号里声明的叫「组件」(components),你把字段往那一放,编译器自动给你生成:
- 一个全字段的构造方法
- 每个字段的访问方法(注意:是 x() 不是 getX(),record 用的是字段同名方法)
- equals 和 hashCode(自动基于所有字段)
- toString(自动列出所有字段)
而且字段自动是 private final 的,record 本身不可变。用起来:
Point p = new Point(1, 2);
System.out.println(p.x()); // 1 —— 访问方法是 x(),不是 getX()
System.out.println(p); // Point[x=1, y=2] —— 自动 toString
System.out.println(p.equals(new Point(1, 2))); // true —— 自动 equals
最爽的是「加字段」这件事:你想加个 z,就把 record 改成 record Point(int x, int y, int z) {},构造方法、访问方法、equals、hashCode、toString 全自动跟着更新,绝无遗漏。再也不会出现「equals 忘了带新字段」那种隐蔽 bug。
record 能做的不止这些
别以为 record 只能当个空壳数据袋,它还能加东西,只是有边界。
可以加方法。record 里能定义普通方法、静态方法、静态字段:
public record Point(int x, int y) {
// 加个实例方法
public double distanceTo(Point other) {
int dx = x - other.x(), dy = y - other.y();
return Math.sqrt(dx * dx + dy * dy);
// 加个静态工厂方法
public static Point origin() {
return new Point(0, 0);
可以校验参数。用「紧凑构造方法」(compact constructor)能在构造时做校验,不用写完整的构造方法签名:
public record Range(int start, int end) {
public Range { // 紧凑构造方法,没有参数列表
if (start > end) {
throw new IllegalArgumentException("start 不能大于 end");
// 不用写 this.start = start,编译器自动补上赋值
那个 public Range { ... } 没有参数列表,是 record 特有的紧凑写法。你在里面写校验逻辑,赋值语句编译器帮你补。这是给数据加约束的标准姿势。
什么时候别用 record
record 虽好,但它有明确的适用边界,不是所有类都该换。它的设计定位就是「不可变的数据载体」,以下情况别用:
情况
为什么不适合 record
需要可变状态
record 字段全是 final,天生不可变,要改字段就别用
需要继承别的类
record 已经隐式继承了 Record,不能再 extends 别的类
是个有行为的领域对象
record 适合"装数据",重逻辑的业务实体用普通类更合适
JPA 实体
JPA 一般要求无参构造和可变字段,record 不满足
简单说,record 是给 DTO、VO、配置项、多返回值打包、值对象这类「纯数据、不可变」的场景用的。如果一个类有复杂的可变状态、有大量业务行为、需要继承体系,那它不是数据载体,老老实实用普通类。
实际项目里,我们的判断很简单:这个类是不是「就为了把几个值打包传来传去、且不需要中途修改」?是,就 record;不是,就普通类。接口的请求响应 DTO、查询返回的 VO、方法要返回多个值时的打包,这些全换成了 record,代码瞬间清爽。
半年用下来,record 给我最大的价值不只是「少写代码」,更是「少出错」——那些靠手动维护 equals/hashCode 一致性的隐患,全没了,编译器替你保证。如果你的项目还在 Java 16+ 上手写一堆 POJO 样板,真该试试 record,把省下来的精力放到真正重要的业务逻辑上。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.