在现代 Java 开发中,业务模型往往需要既能保持不可变性,又能灵活处理多种类型的数据。Java 21 为此提供了两大核心特性:Record 和 Pattern 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 业务代码。
