痛点:写一个不可变数据类很啰嗦
public class PointOld {
private final int x;
private final int y;
public PointOld(int x, int y) {
this.x = x;
this.y = y;
}
public int x() { return x; }
public int y() { return y; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PointOld p)) return false;
return x == p.x && y == p.y;
}
@Override
public int hashCode() { return Objects.hash(x, y); }
@Override
public String toString() { return "PointOld[x=" + x + ", y=" + y + "]"; }
}
20 行写一个二维点。
record(Java 14 预览 / 16 正式)
public record Point(int x, int y) {}
就这一行。编译器自动生成:
- 私有 final 字段
x、y - 构造函数
Point(int x, int y) - 访问器
x()/y()(注意不是 getX()) equals/hashCode(按值比较)toString
var p = new Point(1, 2);
p.x(); // 1
System.out.println(p); // Point[x=1, y=2]
var p2 = new Point(1, 2);
p.equals(p2); // true
p == p2; // false(不同对象)
加方法
public record Point(int x, int y) {
public double distanceTo(Point other) {
return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(y - other.y, 2));
}
public static Point origin() {
return new Point(0, 0);
}
}
record 体里可以加方法、静态字段、静态方法、嵌套类型。
compact 构造(验证 / 标准化)
public record Email(String address) {
public Email { // 紧凑构造(注意没参数列表)
if (address == null || !address.contains("@"))
throw new IllegalArgumentException("invalid email");
address = address.toLowerCase(); // 改的是参数,最后自动赋字段
}
}
new Email("ALICE@example.COM").address(); // "alice@example.com"
紧凑构造在标准构造之前运行——验证、标准化、规范化字段值。
record 不能继承
public record Foo() {}
public class Bar extends Foo {} // ❌ record 默认 final
record 隐式 final——可以实现接口但不能被继承。这是设计:record 表"不可变数据",继承会破坏值语义。
实现接口
public interface Sized {
int size();
}
public record Box(int width, int height) implements Sized {
@Override
public int size() { return width * height; }
}
sealed class(Java 17 正式)
public sealed interface Shape permits Circle, Square, Triangle {}
public record Circle(double radius) implements Shape {}
public record Square(double side) implements Shape {}
public record Triangle(double base, double height) implements Shape {}
sealed = "限定只能这几个类继承 / 实现我"。
permits 列表里的类必须显式声明 final / sealed / non-sealed:
public final class Circle implements Shape { ... } // 最终
public sealed class Square implements Shape permits ... // 继续封闭
public non-sealed class Triangle implements Shape { ... } // 重新开放
record 自动 final,所以用 record 实现 sealed 接口最干净。
sealed + pattern matching = 代数数据类型
public sealed interface Result<T>
permits Result.Success, Result.Failure {
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error) implements Result<T> {}
}
// 使用
Result<User> r = repo.findUser(1);
String msg = switch (r) {
case Success<User> s -> "Got " + s.value().name();
case Failure<User> f -> "Error: " + f.error();
// 编译器知道 sealed → 不用 default
};
详见 第 17 篇。
record 的限制
- 字段全是 final——不能改(这就是设计意图)
- 不能加实例字段(构造里设的就是全部)
- 不能继承类(隐式 final,且只能 implements,不能 extends)
- 不能定义实例初始化器(但可以静态初始化器)
如果需要可变状态或继承——用 class。
什么时候用 record
| 适用 | 不适用 |
|---|---|
| DTO / API 模型 | 有可变状态的实体(User with mutable email) |
| 配置 / 选项 | 复杂业务对象有大量方法 |
| 值对象(Money / Point / Vec) | 需要继承层级(用 sealed class) |
| Map.Entry / Pair / Tuple 替代 | JPA 实体(不能加 setter / @Entity 注解通常和 record 不兼容) |
| 模式匹配的分支类型 |
常见模式
多字段验证
public record User(String name, String email, int age) {
public User {
if (name == null || name.isBlank()) throw new IllegalArgumentException();
if (email == null || !email.contains("@")) throw new IllegalArgumentException();
if (age < 0 || age > 150) throw new IllegalArgumentException();
}
}
Builder(很少用,因为 record 字段少)
public record User(String name, String email, int age) {
public static class Builder {
private String name;
private String email;
private int age;
public Builder name(String n) { this.name = n; return this; }
public Builder email(String e) { this.email = e; return this; }
public Builder age(int a) { this.age = a; return this; }
public User build() { return new User(name, email, age); }
}
}
User u = new User.Builder().name("A").email("a@x").age(30).build();
通常 record 字段少(< 5),直接 new User("A", "a@x", 30) 就够——构造命名参数 IDE 提示也清楚。
选项类型 / 错误处理
public sealed interface Maybe<T>
permits Maybe.Some, Maybe.None {
record Some<T>(T value) implements Maybe<T> {}
record None<T>() implements Maybe<T> {}
}
// 用 switch + pattern
return switch (maybe) {
case Maybe.Some<String> s -> s.value();
case Maybe.None<String> n -> "default";
};
record vs class 选择
// ✅ record:纯数据,自动生成所有"管道"代码
public record UserDto(int id, String name, String email) {}
// ✅ class:业务实体,有行为,有可变状态
public class User {
private int id;
private String email;
public void changeEmail(String newEmail) { ... }
}
经验法则:
- 字段都是 final 的数据载体 → record
- 有行为 + 可变状态 + 继承关系 → class
→ 下一篇 泛型