写 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,把省下来的精力放到真正重要的业务逻辑上。