Record 与不可变
Java 16+ record 类型。
25 分钟
Record 与不可变
这一课你将能做到
- 用一行声明定义带校验的 record DTO,并解释编译器自动生成了哪些成员
- 处理 record 的不可变边界:紧凑构造器校验、可变组件防御性拷贝、with 风格更新
- 判断什么场景该用 record、什么场景必须退回 class(JPA Entity、可变状态)
为什么要学
传统 DTO 一眼望去 80 行:字段、getter/setter、构造器、equals、hashCode、toString——全是机器能生成的噪音。record 把它压成一行,还顺手给了不可变保证:字段天然 final,作 Map key、跨线程传递都不会被半路篡改。Spring Boot 新代码里 record 已经是 Controller 入参出参、MyBatis 查询投影的默认选择。
面试角度:「record 的本质是什么」「record 能被继承吗」「record 和 Lombok @Value 的区别」「为什么 JPA Entity 不用 record」是 JDK 17+ 迁移必考题,本课按能背的标准给出答案。
先建立图像
一行声明:
public record LessonView(String slug, String title, int orderIndex) {}
编译器眼中它等价于:
public final class LessonView extends java.lang.Record {
private final String slug;
private final String title;
private final int orderIndex;
public LessonView(String slug, String title, int orderIndex) { /* 全参构造 */ }
public String slug() { return slug; } // 访问器不带 get 前缀
public String title() { return title; }
public int orderIndex() { return orderIndex; }
@Override public boolean equals(Object o) { /* 全组件参与判等 */ }
@Override public int hashCode() { /* 全组件参与 */ }
@Override public String toString() { /* LessonView[slug=..., title=..., orderIndex=...] */ }
}
注意三点:隐式 final(不能被继承)、访问器名与组件同名、没有 setter——不可变是一等公民。
跟着做
步骤 1:完整示例——紧凑构造器、静态工厂、with 风格
新建 RecordDemo.java,完整可运行:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RecordDemo {
/** DTO:一行声明 + 紧凑构造器校验 */
record LessonView(String slug, String title, int orderIndex) {
LessonView { // 紧凑构造器:在字段赋值前执行,直接改写/校验参数
if (slug == null || slug.isBlank())
throw new IllegalArgumentException("slug 不能为空");
if (orderIndex < 0)
throw new IllegalArgumentException("orderIndex 不能为负");
title = (title == null || title.isBlank()) ? slug : title;
}
/** 静态工厂:语义化创建入口 */
static LessonView draft(String slug) {
return new LessonView(slug, slug, 0);
}
/** 不可变对象的「修改」= 返回新实例 */
LessonView withOrder(int newIndex) {
return new LessonView(slug, title, newIndex);
}
}
public static void main(String[] args) {
LessonView v = new LessonView("jva-records", "Record 与不可变", 6);
System.out.println(v.slug()); // jva-records
System.out.println(v.withOrder(7)); // LessonView[slug=jva-records, title=Record 与不可变, orderIndex=7]
// 全组件参与判等 ⇒ 值语义
System.out.println(v.equals(new LessonView("jva-records", "Record 与不可变", 6))); // true
// 值相等 ⇒ 可安全作 Map key
Map<LessonView, String> cache = new HashMap<>();
cache.put(new LessonView("jva-oop", "OOP 进阶", 4), "cached");
System.out.println(cache.get(new LessonView("jva-oop", "OOP 进阶", 4))); // cached
System.out.println(LessonView.draft("jva-new"));
}
}