JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · LANGUAGE · JEP 395

record:透明数据载体

JDK 17 · 速查主题 01 / 06 · 预计 18 分钟
核心18 分钟JEP 395
一句话record 用一行声明描述一组值:字段、访问器、equals/hashCode/toString 都由编译器生成。它的价值是「透明」——你能从声明直接读出对象状态,也因此不适合承载隐藏状态或复杂生命周期。

学完你要能自己回答

  1. record 自动生成了哪些成员?紧凑构造器与规范构造器分别做什么?
  2. record 的不可变边界在哪里?List 或数组组件要怎么处理?
  3. record 能实现接口、能继承类吗?它和 Lombok 生成的类本质区别是什么?

声明即契约01

record 的组件列表同时定义了字段、构造器参数、访问器和 equals/hashCode/toString 的输入。声明是 final class,隐式继承 java.lang.Record,组件字段是 private final。

public record Point(int x, int y) {
    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException("坐标必须非负");
        }
    }

    public Point translate(int dx, int dy) {
        return new Point(x + dx, y + dy);
    }
}
var p = new Point(1, 2);
System.out.println(p);
System.out.println(p.translate(1, 1));
System.out.println(p.x() + "," + p.y());
Point[x=1, y=2]
Point[x=2, y=3]
1,2
  • 自动生成:规范构造器、每个组件的同名访问器、equals、hashCode、toString。
  • 访问器是 x(),不是 getX();toString 的格式是 Point[x=1, y=2]。
  • 可以声明额外方法、静态字段和静态方法,但不能声明额外的实例字段。
  • record 不能继承其他类,但可以实现接口;它本身是 final。
record 适合 DTO、返回值、Map 的复合 key、模式匹配的载体。需要 setter、代理、懒加载或复杂生命周期的对象,不要硬套 record。

紧凑构造器:校验与归一化02

规范构造器的签名必须与组件列表一致。紧凑构造器省略参数列表,参数名与组件同名;编译器会在构造器末尾自动插入字段赋值,你只需要写校验或归一化。

public record Range(int from, int to) {
    public Range {
        if (from > to) {
            throw new IllegalArgumentException("from > to");
        }
    }

    public int length() {
        return to - from;
    }
}
var r = new Range(3, 8);
System.out.println(r);
System.out.println(r.length());
try {
    new Range(5, 1);
} catch (IllegalArgumentException e) {
    System.out.println(e.getMessage());
}
Range[from=3, to=8]
5
from > to

在紧凑构造器里写 from = ... 是在给隐式参数赋值;不能写 this.from = from,字段在构造器体里仍是 final 的未赋值状态,编译器会报 cannot assign a value to final variable。

紧凑构造器适合做参数校验、范围归一化和防御性拷贝。不要在里面做 I/O、查库或昂贵计算:构造器应该快速失败,而不是产生副作用。

不可变是浅的03

record 只保证组件引用不可变,不保证引用指向的对象不可变。List 组件仍可能被外部修改;数组组件的问题更隐蔽,连 equals 都按引用比较。

public record Team(String name, List<String> members) {
    public Team {
        members = List.copyOf(members);
    }
}
var source = new ArrayList<>(List.of("a"));
var team = new Team("core", source);
source.add("b");
System.out.println(team.members().size());
try {
    new Team("x", null);
} catch (NullPointerException e) {
    System.out.println("null rejected");
}
1
null rejected
  • List.copyOf 返回不可变副本,并拒绝 null;需要保留 null 时不能用它。
  • 数组组件要在紧凑构造器和访问器里 clone();record 生成的 equals 对数组按引用比较,内容相同也可能返回 false。
  • 需要深不可变时,组件本身也必须是不可变类型;List<Mutable> 仍然是浅不可变。
  • record 的 hashCode 同样基于组件;数组组件会使用身份哈希,不适合作为 Map key。
record 不是「自动线程安全」的同义词。它只消除了字段重新赋值,不消除共享可变对象带来的竞态。

能力边界04

可以做不可以做
实现接口、声明额外方法声明额外的实例字段
声明静态字段、静态方法继承其他类(已隐式继承 Record)
定义泛型 record、局部 record、嵌套 record定义可变的 setter 语义
重写访问器、equals、hashCode、toString让 record 成为非 final 类
作为 sealed 层级的 final 子类型依赖无参构造器的框架(如部分 JPA 实体)
  • 局部 record 是隐式的 static,适合方法内临时聚合返回值。
  • record 可以实现 sealed 接口,因为它天然是 final,满足 sealed 对子类型的要求。
  • 显式重写访问器可以返回派生值或防御性副本,但每次调用都会执行,别放昂贵计算。
  • record 的序列化使用规范构造器,反序列化会走校验逻辑;这与普通类的默认反序列化不同。
判断是否用 record,只看一条:对象的状态是否完全由组件列表公开决定。如果还有缓存、连接、监听器或生命周期状态,普通类更合适。

运行时形态05

record 不是语法糖生成的普通类:JVM 与反射 API 都能识别它。Class.isRecord()、getRecordComponents() 是标准接口,框架可以据此做序列化、绑定和校验。

System.out.println(Point.class.isRecord());
System.out.println(java.lang.reflect.Modifier.isFinal(Point.class.getModifiers()));
System.out.println(Point.class.getRecordComponents().length);
true
true
2
$ javap -p Point.class
Compiled from "Point.java"
public final class Point extends java.lang.Record {
  private final int x;
  private final int y;
  public Point(int, int);
  public Point translate(int, int);
  public final java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int x();
  public int y();
}

equals / hashCode / toString 的方法体不是手写逻辑,而是通过 invokedynamic 链接到 java.lang.runtime.ObjectMethods 的启动方法,由它按组件生成行为。这也是 record 的语义能被运行时统一识别的原因。

17 没有 record pattern。o instanceof Point(int x, int y) 在 17 连预览都不支持;解构要等到 21(JEP 440)。

版本边界BOUNDARY

这张表比 API 本身更值得记。写代码时先确认你的运行时版本。

特性 / API状态说明
record16 正式JEP 395;JDK 14 / 15 预览。
record pattern21 正式17 不支持,连预览都没有;19 / 20 预览。
局部 record16 正式方法内定义,隐式 static。
record 实现 sealed 接口17 可用record 天然 final,可作为 sealed 的最终子类型。
record 数组组件的 equals语义固定按引用比较,内容相同也可能为 false;需要自定义。

费曼自检SELF CHECK

先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。

01. record 自动生成了什么?为什么不能声明额外实例字段?
生成规范构造器、同名访问器、equals、hashCode、toString。record 的状态必须完全由组件列表定义,额外实例字段会破坏这种透明性,所以语言直接禁止。
02. 紧凑构造器和规范构造器有什么区别?
规范构造器写出完整参数列表;紧凑构造器省略参数列表,参数名与组件同名,编译器在末尾插入字段赋值。紧凑构造器适合校验和归一化,不能写 this.x = x。
03. record 的 List 组件为什么还需要防御性拷贝?
record 只保证引用不被重新赋值,List 内容仍可被外部修改。应在紧凑构造器里 List.copyOf,访问器里也可返回副本;数组还要 clone,并注意 equals 按引用比较。
04. record 和 Lombok 的 @Value 有什么本质区别?
record 是语言规范的一部分,编译为 final class 并继承 java.lang.Record,语义固定、可被反射识别、可作为模式匹配载体;Lombok 是编译期生成代码,产物仍是普通类,行为可配置。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。