Java Records:不可变数据载体

FreeGuideOnline 最新 2026-06-17

什么是 Java Record

Java Record 是一种特殊的类,从 Java 16 开始正式成为语言标准。它专门用于充当不可变的数据载体,旨在减少样板代码,让开发者用一行声明就能定义出只包含数据的类。

传统上,要创建一个简单的数据类(如 DTO、值对象),你需要手动编写构造器、equals()hashCode()toString()以及各字段的 getter,这些代码冗长且容易出错。Record 可以自动生成这些方法,让你专注在数据本身。

快速入门:创建你的第一个 Record

Record 使用 record 关键字声明,后面紧跟名称和括号内的组件列表(components)。组件的类型和名称一起定义了该 Record 的状态。

public record Point(int x, int y) { }

以上一行代码就完成了传统需要几十行的 Point 类。编译器会自动生成:

  • x()y() 两个访问器方法(而不是 getX()getY()
  • 一个规范构造器 Point(int x, int y)
  • equals(Object o)hashCode()toString() 方法

使用起来也像普通类一样:

Point p = new Point(3, 4);
System.out.println(p.x());      // 3
System.out.println(p);          // Point[x=3, y=4]

自动生成的方法详解

访问器(Accessor)

Record 为每个组件生成一个与组件同名的公共方法,没有 get 前缀。例如 x()y()。这是 Java 命名惯例的简化,体现了“数据即方法”的思维。

规范构造器

Record 自动生成的构造器会接收全部组件作为参数,并顺序赋值。你可以在不声明构造器的情况下直接使用:

var p = new Point(5, 10);

equals() 与 hashCode()

自动生成的 equals() 方法会基于所有组件进行比较,只要两个 Record 实例的对应组件值相等,它们就相等。hashCode() 也基于所有组件生成,保证一致性。

toString()

返回格式固定为 ClassName[component1=value1, component2=value2, ...],非常适合日志调试。

自定义 Record 行为

Record 并不只是“只读数据壳”,你也可以添加自定义的方法和行为,但必须遵守不可变性原则。

紧凑构造器(Compact Constructor)

你可以对组件进行验证或规范化处理,而无需重新声明参数列表。这种构造器没有参数括号,直接访问组件:

public record Range(int start, int end) {
    public Range {
        if (start > end) {
            int temp = start;
            start = end;
            end = temp;
        }
        // 这里自动将参数赋值给字段
    }
}

紧凑构造器中的代码在自动生成的赋值操作之前执行。赋给 startend 实际上是在修改参数值,最终这些值会被赋给底层字段。

重写规范构造器

你也可以写完整的规范构造器,但必须手动赋值给所有组件:

public record Range(int start, int end) {
    public Range(int start, int end) {
        if (start > end) throw new IllegalArgumentException("start must be <= end");
        this.start = start;
        this.end = end;
    }
}

添加静态字段与方法

你可以像普通类一样添加静态字段、静态方法或泛型方法:

public record Circle(double radius) {
    public static final double PI = 3.1415926;

    public double area() {
        return PI * radius * radius;
    }

    public static Circle fromDiameter(double diameter) {
        return new Circle(diameter / 2);
    }
}

实例方法可以任意访问组件,但不能修改它们(因为字段是 final 的)。

不可变性的保障

Record 的所有组件字段都是 private final,没有 setter 方法,保证了实例一旦创建就无法被改变。这天然适合作为值对象、缓存键、多线程共享数据。

Record 的限制与特性

为了保持“纯粹的不可变数据载体”定位,Record 有以下限制:

  • 隐式 final:Record 是隐式的最终类,不能被其他类继承。
  • 不能显式继承其他类:每个 Record 都隐式继承 java.lang.Record,因此不能再继承其他类。
  • 可以实现接口:Record 可以像普通类一样实现任意接口。
  • 不能声明非静态字段:实例字段只能是组件列表中的字段,不允许额外定义实例变量(但静态变量可以)。
  • 构造器必须调用其他构造器或最终赋值全部组件:无论你加了什么构造器,最终都需要确保所有组件字段被赋值。
  • 没有默认构造器:因为组件列表定义了全部状态,不存在无参构造器。

这些限制确保了 Record 在任何场景下都保持语义明确、行为可预测。

何时应该使用 Record?

  • 数据传输对象(DTO):在不同层之间传递纯数据。
  • 值对象:坐标、货币金额、范围、颜色等由几个值唯一确定的对象。
  • 返回多个值:方法需要返回多个同类型结果时,可以用 Record 包装(替代 Pair/Tuple)。
  • 配置或不可变快照:保存某一时刻的状态。
  • 作为 Map 的键:因为 equals/hashCode 基于值自动生成,非常适合作为键。

不适合的场景:

  • 需要可变字段的实体类(如 JPA 实体,除非项目框架支持 Record)。
  • 需要复杂的继承层次。
  • 字段不固定、需要动态添加属性的容器。

与传统类及 Lombok 的对比

特性 传统 POJO Lombok @Value Record
代码量 很多(构造器、getter、equals等) 一行注解 一行声明
不可变性 需手动保证 自动将所有字段设为 private final 自动 final 字段,无 setter
继承 可继承其他类 可继承其他类 默认 final,不能继承类
字段命名风格 getX() getX() x()
语言支持 纯 Java 依赖第三方库 原生 Java,无额外依赖

Record 是 Java 语言层面的官方解决方案,比 Lombok 更加轻量和标准化,没有字节码黑魔法,调试起来更直观。

进阶:Record 中的泛型与嵌套

Record 完美支持泛型,可以创建可复用的数据容器:

public record Pair<K, V>(K key, V value) { }

Pair<String, Integer> pair = new Pair<>("age", 30);

Record 内部也可以嵌套其他 Record:

public record Address(String city, String street) { }
public record Customer(String name, Address address) { }

总结

Java Record 是编写不可变数据类的最佳实践方式。它通过简明的语法消除冗余代码,内建标准化语义,让开发者更关注数据本身的设计与业务逻辑。对于任何表示“纯数据”的场景,优先考虑使用 Record,你的代码将更加简洁、安全且易于维护。