前置知识: Java

Java记录类

00:00
3 min Intermediate 2026/6/14

Record类与密封接口

概述

Record 类(Java 16 正式)和密封接口(Java 17 正式)是 Java 面向数据建模的两个重要特性。Record 类用于定义不可变的数据载体,自动生成样板代码;密封接口限制继承层级,使类型系统更安全。两者结合使用,可以构建优雅且类型安全的数据模型。

基础概念

Record 类的特点

  • 不可变:所有字段都是 final
  • 自动生成:构造器、getter(x() 而非 getX())、equals、hashCode、toString
  • 不能继承其他类,但可以实现接口
  • 可以声明实例方法和静态方法
  • 可以声明紧凑构造器参数验证

密封类的特点

  • 使用 sealed 关键字修饰,通过 permits 列出允许子类
  • 子类必须是 final、sealed 或 non-sealed
  • 编译器可以穷举所有子类型,配合模式匹配使用
  • Record 结合可以构建代数数据类型(ADT)

快速上手

Record 基本用法

// 定义 Record 类
public record Point(int x, int y) {
    // 紧凑构造器:用于参数验证
    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException("坐标不能为负数");
        }
    }
}

// 使用 Record
var p = new Point(3, 4);
p.x();   // 3(注意:不是 getX())
p.y();   // 4
p.toString(); // Point[x=3, y=4]

// 自动生成的 equals 和 hashCode
new Point(1, 2).equals(new Point(1, 2)); // true

// 解构(Java 21 预览特性)
// if (p instanceof Point(int x, int y)) { ... }

密封接口基本用法

// 定义密封接口,限制实现类
public sealed interface Shape permits Circle, Rectangle, Triangle {}

// 实现类使用 record(最简洁的方式)
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}

// 实现类使用普通类
public non-sealed class Triangle implements Shape {
    private final double a, b, c;
    public Triangle(double a, double b, double c) {
        this.a = a; this.b = b; this.c = c;
    }
}

详细用法

Record 高级特性

// Record 实现接口
public record User(Long id, String name, String email)
    implements Comparable<User> {

    // 自定义方法
    public String displayName() {
        return name + " <" + email + ">";
    }

    // 静态工厂方法
    public static User anonymous() {
        return new User(0L, "Anonymous", "anon@example.com");
    }

    // 静态字段(允许)
    public static final User SYSTEM = new User(-1L, "System", "system@example.com");

    // 实现 Comparable
    @Override
    public int compareTo(User other) {
        return this.name.compareTo(other.name);
    }
}

Record 与泛型

// 泛型 Record
public record Pair<K, V>(K key, V value) {
    // 紧凑构造器中可以修改参数
    public Pair {
        Objects.requireNonNull(key);
        Objects.requireNonNull(value);
    }

    // 交换键值
    public Pair<V, K> swap() {
        return new Pair<>(value, key);
    }
}

// 使用泛型 Record
Pair<String, Integer> entry = new Pair<>("age", 25);
Pair<Integer, String> swapped = entry.swap();

Record 与 JSON 序列化

// Record 与 Jackson 配合(Jackson 2.12+ 原生支持)
public record UserDTO(
    Long id,
    @JsonProperty("user_name") String name,
    @JsonProperty("email_address") String email,
    @JsonFormat(pattern = "yyyy-MM-dd") LocalDate createdAt
) {}

// Spring Controller 中直接使用 Record
@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping
    public UserDTO createUser(@RequestBody UserDTO dto) {
        return userService.create(dto);
    }
}

密封类与模式匹配

// 密封类配合 switch 模式匹配(Java 21)
public static double area(Shape shape) {
    return switch (shape) {
        case Circle(var r) -> Math.PI * r * r;
        case Rectangle(var w, var h) -> w * h;
        case Triangle t -> heronFormula(t);
    };
    // 不需要 default,编译器知道所有子类型
}

// 带守卫条件的模式匹配
public static String describe(Shape shape) {
    return switch (shape) {
        case Circle(var r) when r > 10 -> "大圆";
        case Circle(var r) -> "小圆";
        case Rectangle(var w, var h) when w == h -> "正方形";
        case Rectangle(var w, var h) -> "矩形 " + w + "x" + h;
        case Triangle t -> "三角形";
    };
}

常见场景

Record 替代 DTO 和值对象

// API 响应使用 Record
public record ApiResponse<T>(
    int code,
    String message,
    T data,
    LocalDateTime timestamp
) {
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "成功", data, LocalDateTime.now());
    }

    public static <T> ApiResponse<T> error(int code, String message) {
        return new ApiResponse<>(code, message, null, LocalDateTime.now());
    }
}

// 分页响应
public record PageResponse<T>(
    List<T> content,
    int page,
    int size,
    long totalElements,
    int totalPages
) {}

领域事件建模

// 使用密封接口 + Record 建模领域事件
public sealed interface OrderEvent {
    String orderId();
    LocalDateTime timestamp();
}

public record OrderCreated(
    String orderId,
    String userId,
    BigDecimal amount,
    LocalDateTime timestamp
) implements OrderEvent {}

public record OrderPaid(
    String orderId,
    String paymentMethod,
    LocalDateTime timestamp
) implements OrderEvent {}

public record OrderShipped(
    String orderId,
    String trackingNumber,
    LocalDateTime timestamp
) implements OrderEvent {}

public record OrderCancelled(
    String orderId,
    String reason,
    LocalDateTime timestamp
) implements OrderEvent {}

// 处理事件时编译器保证穷举
public void handleEvent(OrderEvent event) {
    switch (event) {
        case OrderCreated(var id, var uid, var amt, var ts)
            -> processCreation(id, uid, amt);
        case OrderPaid(var id, var method, var ts)
            -> processPayment(id, method);
        case OrderShipped(var id, var tracking, var ts)
            -> processShipment(id, tracking);
        case OrderCancelled(var id, var reason, var ts)
            -> processCancellation(id, reason);
    }
}

注意事项

  • Record字段是不可变的,不能有 setter 方法
  • Record 不能继承其他(隐式继承 java.lang.Record
  • Record 的紧凑构造器中修改参数影响,但字段本身仍不可变
  • 密封类的 permits 列表中的必须在同一中(模块化项目中可在同一模块
  • Record 不能声明实例字段(只能通过构造器参数定义),但可以声明静态字段
  • Recordgetter 方法名是字段名(如 x()),不是 getX(),注意与 Bean 规范的区别

进阶用法

Record 与序列化代理模式

// 使用 Record 实现序列化代理模式
public class User implements Serializable {
    private final Long id;
    private final String name;
    private transient String password; // 不序列化

    // 序列化代理
    private record SerializationProxy(Long id, String name) {}

    private Object writeReplace() {
        return new SerializationProxy(id, name);
    }

    private void readObject(ObjectInputStream stream)
            throws InvalidObjectException {
        throw new InvalidObjectException("请使用序列化代理");
    }

    private User(SerializationProxy proxy) {
        this.id = proxy.id();
        this.name = proxy.name();
    }
}

局部 Record

// 在方法内定义局部 Record,适合临时数据转换
public List<OrderSummary> summarize(List<Order> orders) {
    // 局部 Record,仅在此方法内可见
    record TempSummary(String category, BigDecimal total, int count) {}

    Map<String, TempSummary> summaries = orders.stream()
        .collect(Collectors.groupingBy(Order::getCategory,
            Collectors.collectingAndThen(
                Collectors.toList(),
                list -> new TempSummary(
                    list.get(0).getCategory(),
                    list.stream().map(Order::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add),
                    list.size()
                )
            )));

    return summaries.values().stream()
        .map(ts -> new OrderSummary(ts.category(), ts.total(), ts.count()))
        .toList();
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式