痛点:写一个不可变数据类很啰嗦

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 字段 xy
  • 构造函数 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

→ 下一篇 泛型