从零开始:Java 21 的 Record 与 Pattern Matching 在业务模型设计中的实践

在现代 Java 开发中,业务模型往往需要既能保持不可变性,又能灵活处理多种类型的数据。Java 21 为此提供了两大核心特性:RecordPattern Matching(以及其在 switch 语句中的新语法)。这两者配合使用,不仅能显著降低样板代码,还能让业务逻辑更加直观、安全。本文将从实际项目需求出发,演示如何利用这两项特性构建可维护、高效的业务模型。


1. Record:轻量级不可变数据载体

Record 是 Java 16 引入的一个语言级别的轻量级不可变类。它在编译时会自动生成:

  • 私有 final 字段
  • 构造器
  • equals()hashCode()toString() 方法
  • Getter 方法(不带 get 前缀)
public record User(Long id, String username, String email) {}

1.1 为什么选择 Record?

特性 传统 POJO Record
字段定义 需要手动声明为 private final 自动完成
Getter 手动编写或使用 Lombok 自动生成
equals/hashCode/toString 手动实现 自动实现
线程安全 需要手动维护 原生不可变
编译速度 需要编译器扫描注解 直接编译为字节码

Record 的不可变性天然符合业务实体的需求,尤其在分布式系统中传递数据时避免了共享状态导致的并发问题。

1.2 记录的扩展能力

虽然 Record 本身是不可变的,但可以通过 with 语法(Java 17 之后新增)创建修改后的新实例,保持不可变链路:

User u1 = new User(1L, "alice", "[email protected]");
User u2 = u1.with(email("[email protected]"));

2. Pattern Matching:更安全、更简洁的类型检查

Pattern Matching 为 instanceof 提供了类型绑定的能力,减少了显式类型转换:

Object obj = fetchFromCache();

if (obj instanceof String s) {
    // 直接使用 s,而不需要再强制转换
    System.out.println(s.toUpperCase());
}

2.1 在 switch 中的使用

Java 21 的 switch 语句支持 pattern,可以一次性匹配多种类型,甚至可以嵌套模式:

Object value = fetchValue();

String result = switch (value) {
    case Integer i -> "整型: " + i;
    case String s  -> "字符串: " + s;
    case null      -> "空值";
    default        -> "其他类型: " + value.getClass().getSimpleName();
};
System.out.println(result);

2.2 与 Record 搭配

Record 结合 Pattern Matching 可以实现“数据解构”,极大提升代码可读性:

record Order(Long id, User buyer, Double total) {}

Object data = fetchOrder();

if (data instanceof Order(Long id, User buyer, Double total)) {
    System.out.printf("订单 #%d 由 %s 购买,金额 %.2f%n", id, buyer.username(), total);
}

3. 实战案例:构建一个订单系统

3.1 业务模型

public sealed interface Payment permits CreditCard, PayPal, Balance {}

public record CreditCard(String number, String owner, String exp) implements Payment {}
public record PayPal(String email) implements Payment {}
public record Balance(Long userId) implements Payment {}

public record Order(
        Long id,
        User buyer,
        List <Item> items,
        Payment payment,
        OrderStatus status) {}
  • sealed 接口保证所有实现类在编译期已知,Pattern Matching 能够完整覆盖。
  • Order 中的 items 采用 `List `,而 `Item` 也可以定义为 Record。

3.2 处理订单逻辑

public class OrderService {

    public void process(Order order) {
        // 1. 校验支付方式
        switch (order.payment()) {
            case CreditCard(String number, String owner, String exp) ->
                validateCard(number, owner, exp);
            case PayPal(String email) ->
                validatePayPal(email);
            case Balance(Long uid) ->
                validateBalance(uid, order.totalAmount());
            case null ->
                throw new IllegalStateException("未提供支付信息");
        }

        // 2. 生成订单号、更新状态
        var confirmed = confirm(order);
        confirmed = confirmed.with(status(OrderStatus.CONFIRMED));

        // 3. 业务后续,例如库存扣减、通知用户等
        // ...
    }
}
  • switch 语句中的 case 语法直接解构 Payment 的具体实现,避免了冗余的 instanceof + 强制转换。
  • with 用于生成状态已更新的新订单实例,保持业务实体不可变。

3.3 单元测试示例

@Test
void testOrderProcessing() {
    User buyer = new User(1001L, "jane", "[email protected]");
    Order order = new Order(
            5001L,
            buyer,
            List.of(new Item(1L, "Book", 2, 15.99)),
            new PayPal("[email protected]"),
            OrderStatus.PENDING);

    var service = new OrderService();
    service.process(order);

    // 验证订单状态已变为 CONFIRMED
    assertEquals(OrderStatus.CONFIRMED, order.status());
}

4. 性能与安全考量

维度 传统 POJO + 显式判断 Record + Pattern Matching
内存 可能存在多余字段、空引用 仅存必要字段,避免空值
GC 更频繁的对象创建 更少的中间对象
可读性 代码冗长 代码更简洁
类型安全 运行时异常风险 编译期类型检查

在大规模订单系统中,使用 Record 及 Pattern Matching 可显著减少对象实例化次数,降低 GC 压力;同时,类型安全的提升让错误更易在编译期被捕获。


5. 结语

Java 21 通过 Record 和 Pattern Matching 的结合,为业务模型提供了更直观、不可变且安全的实现方式。无论是简化实体类的声明,还是提升类型检查的精准度,这两大特性都能在实际项目中带来显著收益。推荐在新项目或重构过程中优先考虑使用 Record + Pattern Matching,打造更健壮、更易维护的 Java 业务代码。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注