Java 17 密封类(Sealed Classes)实战:从类型安全到架构解耦的范式升级
在 Java 17 之前,面向对象编程中的 “类继承” 一直存在一个痛点:一个公开类(public class)可以被任何其他类无限制继承,导致类型体系失控、代码维护成本升高。例如,定义一个 “支付方式” 基类后,开发者可能会随意继承出不符合业务逻辑的子类(如 “现金支付” 继承 “电子支付”),破坏类型体系的完整性。为解决这一问题,Java 17 正式将密封类(Sealed Classes) 纳入标准特性,通过 “显式指定子类范围”“限制继承权限”“强制类型覆盖”,从语言层面实现了类型体系的可控性,为枚举、接口等特性提供了更灵活的补充。本文将从传统类继承的痛点出发,详解密封类的核心概念、语法规则、实战场景及与其他 Java 特性的结合,帮你掌握这一革新性语言特性的最佳实践。
一、为什么需要密封类?—— 传统类继承的 4 大痛点
在理解密封类之前,我们首先要明确:传统 Java 的类继承机制虽然灵活,但在复杂业务场景下,会暴露诸多问题,尤其是在需要严格控制类型体系的场景中。
1.1 痛点 1:类型体系失控(无限制继承)
传统公开类(public class)的继承权限完全开放,任何模块、任何类都可以继承它并扩展功能,导致类型体系混乱。例如,定义一个 “订单状态” 基类:
// 传统公开基类:订单状态
public class OrderStatus {
private String statusCode;
private String statusDesc;
// 构造器、getter、setter
public OrderStatus(String statusCode, String statusDesc) {
this.statusCode = statusCode;
this.statusDesc = statusDesc;
}
// 抽象方法:处理订单状态变更
public abstract void handleStatusChange(Order order);
}
理论上,订单状态应仅包含 “待支付”“已支付”“已取消”“已完成” 4 种,但由于OrderStatus是公开类,其他开发者可能会随意继承出不符合业务逻辑的子类:
// 错误:随意继承OrderStatus,新增“退款中”状态(实际应属于另一类型体系)
public class RefundingStatus extends OrderStatus {
public RefundingStatus() {
super("REFUNDING", "退款中");
}
@Override
public void handleStatusChange(Order order) {
// 处理退款逻辑(与订单状态逻辑冲突)
}
}
这种无限制继承会导致订单状态体系失控,后续在处理订单状态时(如switch判断、状态流转校验),需要额外处理非预期的子类,增加代码复杂度和 bug 风险。
1.2 痛点 2:类型判断不安全(instanceof 滥用)
由于传统类继承无法限制子类范围,在处理类型判断时,往往需要使用instanceof进行多次判断,且无法保证覆盖所有子类,导致类型判断不安全。例如,处理订单状态变更:
// 传统方式:使用instanceof判断订单状态,无法保证覆盖所有子类
public void processOrderStatus(OrderStatus status, Order order) {
if (status instanceof PendingPaymentStatus) {
// 处理待支付状态
((PendingPaymentStatus) status).handleStatusChange(order);
} else if (status instanceof PaidStatus) {
// 处理已支付状态
((PaidStatus) status).handleStatusChange(order);
} else if (status instanceof CancelledStatus) {
// 处理已取消状态
((CancelledStatus) status).handleStatusChange(order);
} else if (status instanceof CompletedStatus) {
// 处理已完成状态
((CompletedStatus) status).handleStatusChange(order);
} else {
// 处理非预期状态(新增子类后易遗漏)
throw new IllegalArgumentException("不支持的订单状态:" + status.getStatusCode());
}
}
这种方式存在两个问题:
- 遗漏风险:新增OrderStatus子类后,若未同步更新processOrderStatus方法,会抛出异常;
- 类型转换冗余:每次instanceof判断后,都需要强制类型转换,代码冗余。
1.3 痛点 3:枚举的灵活性不足
枚举(enum)是传统 Java 中控制类型范围的常用方式,但其灵活性不足 —— 枚举常量无法继承其他类,也无法实现复杂的状态逻辑。例如,用枚举定义订单状态:
// 枚举定义订单状态:无法实现复杂的状态处理逻辑
public enum OrderStatusEnum {
PENDING_PAYMENT("PENDING", "待支付"),
PAID("PAID", "已支付"),
CANCELLED("CANCELLED", "已取消"),
COMPLETED("COMPLETED", "已完成");
private String statusCode;
private String statusDesc;
// 构造器、getter
OrderStatusEnum(String statusCode, String statusDesc) {
this.statusCode = statusCode;
this.statusDesc = statusDesc;
}
// 枚举方法:处理状态变更(逻辑简单,无法扩展复杂逻辑)
public void handleStatusChange(Order order) {
switch (this) {
case PENDING_PAYMENT:
// 待支付逻辑(简单)
order.setPaymentDeadline(LocalDateTime.now().plusHours(2));
break;
case PAID:
// 已支付逻辑(简单)
order.setPaidTime(LocalDateTime.now());
break;
// 其他状态逻辑...
default:
throw new IllegalArgumentException("不支持的状态");
}
}
}
若 “待支付” 状态需要处理复杂逻辑(如发送支付提醒、校验库存),枚举的handleStatusChange方法会变得臃肿,且无法通过继承扩展,只能修改枚举类本身,违反 “开闭原则”。
1.4 痛点 4:接口的实现不可控
接口(interface)虽然可以定义方法契约,但无法限制实现类的范围 —— 任何类都可以实现接口,导致接口的实现类体系失控。例如,定义一个 “支付方式” 接口:
// 支付方式接口
public interface PaymentMethod {
BigDecimal calculateFee(BigDecimal amount); // 计算支付手续费
boolean processPayment(Order order); // 处理支付
}
理论上,支付方式应仅包含 “支付宝”“微信支付”“银联支付” 3 种,但其他开发者可能会实现不符合业务逻辑的类:
// 错误:实现PaymentMethod接口,新增“现金支付”(实际无需计算手续费)
public class CashPayment implements PaymentMethod {
@Override
public BigDecimal calculateFee(BigDecimal amount) {
return BigDecimal.ZERO; // 现金支付无手续费(与接口设计初衷冲突)
}
@Override
public boolean processPayment(Order order) {
// 处理现金支付(无需调用第三方接口,与其他支付方式逻辑差异大)
order.setPaymentMethod("CASH");
return true;
}
}
这种不可控的实现会导致在处理支付方式时(如统一计算手续费、统一异常处理),需要额外适配非预期的实现类,增加代码复杂度。
1.2 密封类的核心价值
Java 17 引入的密封类,通过以下 4 点设计,从根本上解决了传统类继承的痛点:
- 显式子类范围:密封类通过permits关键字显式指定允许继承的子类,禁止其他类继承;
- 类型安全判断:结合 Java 14 引入的 “模式匹配switch”,密封类支持编译期校验类型覆盖,避免instanceof滥用;
- 灵活的状态逻辑:密封类的子类可以继承其他类、实现复杂逻辑,弥补枚举的灵活性不足;
- 可控的实现体系:密封接口(Sealed Interfaces)可以限制实现类范围,解决接口实现不可控的问题。
二、密封类的核心概念与语法规则
密封类的使用围绕 “基类密封”“子类授权”“类型覆盖” 3 个核心概念展开,其语法规则简洁但严谨,掌握这些规则是使用密封类的基础。
2.1 1. 密封类的定义:sealed与permits
密封类通过sealed关键字修饰基类,并通过permits关键字显式指定允许继承的子类,语法格式如下:
// 密封类定义格式
sealed class 基类名 permits 子类1, 子类2, 子类3 {
// 类成员(字段、方法、构造器)
}
关键规则:
- sealed修饰符:基类必须用sealed修饰,表示该类是密封的,仅允许permits指定的子类继承;
- permits子句:permits后必须列出所有允许继承的子类,子类必须与基类在同一模块(Module)中;若基类与子类在同一包中,且子类是顶级类(Top-Level Class),permits子句可省略(编译器会自动推断);
- 子类的修饰符限制:密封类的子类必须使用以下 3 种修饰符之一,明确其继承权限:
-
- final:子类不可再继承(最终类);
-
- sealed:子类本身也是密封类,允许其指定的子类继承;
-
- non-sealed:子类是开放的,允许任何类继承(打破密封)。
示例:定义密封类 “订单状态”
// 密封基类:OrderStatus,允许4个子类继承
sealed class OrderStatus permits PendingPaymentStatus, PaidStatus, CancelledStatus, CompletedStatus {
protected String statusCode;
protected String statusDesc;
// 构造器(子类必须调用)
public OrderStatus(String statusCode, String statusDesc) {
this.statusCode = statusCode;
this.statusDesc = statusDesc;
}
// 抽象方法:处理状态变更(子类必须实现)
public abstract void handleStatusChange(Order order);
// getter
public String getStatusCode() { return statusCode; }
public String getStatusDesc() { return statusDesc; }
}
// 子类1:待支付状态(final,不可再继承)
final class PendingPaymentStatus extends OrderStatus {
public PendingPaymentStatus() {
super("PENDING", "待支付");
}
@Override
public void handleStatusChange(Order order) {
// 复杂逻辑:设置支付截止时间、发送支付提醒
order.setPaymentDeadline(LocalDateTime.now().plusHours(2));
notificationService.sendPaymentReminder(order.getUserId(), order.getOrderId());
}
}
// 子类2:已支付状态(final)
final class PaidStatus extends OrderStatus {
public PaidStatus() {
super("PAID", "已支付");
}
@Override
public void handleStatusChange(Order order) {
// 复杂逻辑:记录支付时间、扣减库存
order.setPaidTime(LocalDateTime.now());
inventoryService.deductStock(order.getOrderItems());
}
}
// 子类3:已取消状态(final)
final class CancelledStatus extends OrderStatus {
public CancelledStatus() {
super("CANCELLED", "已取消");
}
@Override
public void handleStatusChange(Order order) {
// 复杂逻辑:记录取消原因、恢复库存
order.setCancelledTime(LocalDateTime.now());
inventoryService.restoreStock(order.getOrderItems());
}
}
// 子类4:已完成状态(final)
final class CompletedStatus extends OrderStatus {
public CompletedStatus() {
super("COMPLETED", "已完成");
}
@Override
public void handleStatusChange(Order order) {
// 复杂逻辑:记录完成时间、发送完成通知
order.setCompletedTime(LocalDateTime.now());
notificationService.sendCompletionNotice(order.getUserId(), order.getOrderId());
}
}
2.2 2. 密封接口(Sealed Interfaces)
除了密封类,Java 17 还支持密封接口 —— 通过sealed修饰接口,permits指定允许实现的类或子接口,解决传统接口实现不可控的问题。
语法格式:
// 密封接口定义格式
sealed interface 接口名 permits 实现类1, 实现类2, 子接口1 {
// 接口方法
}
关键规则:
- 实现类的修饰符限制:密封接口的实现类必须使用final、sealed或non-sealed修饰;
- 子接口的修饰符限制:密封接口的子接口必须使用sealed或non-sealed修饰(不可用final,因接口不可实例化)。
示例:定义密封接口 “支付方式”
// 密封接口:PaymentMethod,允许3个实现类
sealed interface PaymentMethod permits AlipayPayment, WechatPayment, UnionPayPayment {
// 计算支付手续费(抽象方法)
BigDecimal calculateFee(BigDecimal amount);
// 处理支付(默认方法,可提供通用逻辑)
default boolean processPayment(Order order) {
// 通用逻辑:记录支付日志
paymentLogService.recordLog(order.getOrderId(), this.getClass().getSimpleName());
// 具体逻辑由实现类实现
return doProcessPayment(order);
}
// 抽象方法:具体支付处理
boolean doProcessPayment(Order order);
}
// 实现类1:支付宝支付(final)
final class AlipayPayment implements PaymentMethod {
private static final BigDecimal FEE_RATE = new BigDecimal("0.006"); // 手续费率0.6%
@Override
public BigDecimal calculateFee(BigDecimal amount) {
// 支付宝手续费:金额×0.6%,最低1元
BigDecimal fee = amount.multiply(FEE_RATE);
return fee.compareTo(BigDecimal.ONE) < 0 ? BigDecimal.ONE : fee;
}
@Override
public boolean doProcessPayment(Order order) {
// 调用支付宝API处理支付
AlipayResponse response = alipayClient.execute(new AlipayTradePayRequest(order.getOrderId(), order.getAmount()));
return response.isSuccess();
}
}
// 实现类2:微信支付(final)
final class WechatPayment implements PaymentMethod {
private static final BigDecimal FEE_RATE = new BigDecimal("0.006"); // 手续费率0.6%
@Override
public BigDecimal calculateFee(BigDecimal amount) {
// 微信支付手续费:金额×0.6%,无最低 fee
return amount.multiply(FEE_RATE);
}
@Override
public boolean doProcessPayment(Order order) {
// 调用微信支付API处理支付
WxPayResponse response = wxPayClient.pay(order.getOrderId(), order.getAmount());
return response.getReturnCode().equals("SUCCESS");
}
}
// 实现类3:银联支付(final)
final class UnionPayPayment implements PaymentMethod {
private static final BigDecimal FEE_RATE = new BigDecimal("0.005"); // 手续费率0.5%
@Override
public BigDecimal calculateFee(BigDecimal amount) {
// 银联支付手续费:金额×0.5%,最低0.5元
BigDecimal fee = amount.multiply(FEE_RATE);
return fee.compareTo(new BigDecimal("0.5")) < 0 ? new BigDecimal("0.5") : fee;
}
@Override
public boolean doProcessPayment(Order order) {
// 调用银联支付API处理支付
UnionPayResponse response = unionPayClient.processPayment(order.getOrderId(), order.getAmount());
return response.isSuccess();
}
}
2.3 3. 密封类与模式匹配switch(Java 14+)
密封类的核心优势之一是与 “模式匹配switch”(Java 14 引入,Java 17 正式标准化)结合,实现编译期类型覆盖校验,避免instanceof滥用。
传统instanceof方式 vs 模式匹配switch方式
传统方式(存在遗漏风险):
// 传统方式:使用instanceof判断订单状态,无法保证覆盖所有子类
public void processOrderStatus(OrderStatus status, Order order) {
if (status instanceof PendingPaymentStatus) {
((PendingPaymentStatus) status).handleStatusChange(order);
} else if (status instanceof PaidStatus) {
((PaidStatus) status).handleStatusChange(order);
} else if (status instanceof CancelledStatus) {
((CancelledStatus) status).handleStatusChange(order);
} else if (status instanceof CompletedStatus) {
((CompletedStatus) status).handleStatusChange(order);
} else {
throw new IllegalArgumentException("不支持的订单状态:" + status.getStatusCode());
}
}
模式匹配switch方式(编译期校验覆盖):
// 模式匹配switch:编译期校验是否覆盖所有OrderStatus子类,无遗漏风险
public void processOrderStatus(OrderStatus status, Order order) {
switch (status) {
case PendingPaymentStatus pendingStatus -> pendingStatus.handleStatusChange(order);
case PaidStatus paidStatus -> paidStatus.handleStatusChange(order);
case CancelledStatus cancelledStatus -> cancelledStatus.handleStatusChange(order);
case CompletedStatus completedStatus -> completedStatus.handleStatusChange(order);
// 无需default:编译器会校验是否覆盖所有子类,若新增子类未处理,编译报错
}
}
关键优势:
- 编译期校验:若OrderStatus新增子类(如RefundingStatus),且未在switch中处理,编译器会直接报错,避免运行时异常;
- 无需类型转换:模式匹配switch会自动将status转换为对应子类类型(如PendingPaymentStatus pendingStatus),无需手动强制转换;
- 代码简洁:用->替代case和break,减少模板代码,可读性更高。
三、密封类的实战场景:从业务需求到代码落地
密封类在实际开发中应用广泛,尤其适合 “严格控制类型体系” 的场景。本节将结合 4 个典型业务场景(状态机设计、支付方式管理、数据类型校验、接口实现管控),展示从需求分析到密封类代码实现的完整过程。
3.1 场景 1:订单状态机设计(密封类核心场景)
需求:设计订单状态机,仅允许 “待支付→已支付→已完成”“待支付→已取消” 两种流转路径,禁止其他非法流转(如 “已完成→已取消”),且每个状态需处理复杂的业务逻辑(如支付提醒、库存扣减)。
传统方案(枚举 + 大量if判断)
// 传统方案:枚举定义状态,if判断流转合法性
public enum OrderStatusEnum {
PENDING_PAYMENT("PENDING", "待支付"),
PAID("PAID", "已支付"),
CANCELLED("CANCELLED", "已取消"),
COMPLETED("COMPLETED", "已完成");
// 省略字段、构造器、getter
// 判断状态流转是否合法
public boolean isTransitionAllowed(OrderStatusEnum targetStatus) {
return switch (this) {
case PENDING_PAYMENT -> targetStatus == PAID || targetStatus == CANCELLED;
case PAID -> targetStatus == COMPLETED;
case CANCELLED, COMPLETED -> false; // 已取消/已完成不可流转
};
}
// 处理状态变更(逻辑臃肿,无法扩展)
public void handleStatusChange(Order order) {
switch (this) {
case PENDING_PAYMENT:
order.setPaymentDeadline(LocalDateTime.now().plusHours(2));
break;
case PAID:
order.setPaidTime(LocalDateTime.now());
inventoryService.deductStock(order.getOrderItems());
break;
case CANCELLED:
order.setCancelledTime(LocalDateTime.now());
inventoryService.restoreStock(order.getOrderItems());
break;
case COMPLETED:
order.setCompletedTime(LocalDateTime.now());
break;
}
}
}
// 使用枚举处理订单状态
public void changeOrderStatus(Order order, OrderStatusEnum targetStatus) {
OrderStatusEnum currentStatus = order.getStatus();
// 判断流转是否合法
if (!currentStatus.isTransitionAllowed(targetStatus)) {
throw new IllegalArgumentException("非法状态流转:" + currentStatus + "→" + targetStatus);
}
// 处理状态变更(逻辑在枚举中,无法扩展)
targetStatus.handleStatusChange(order);
// 更新订单状态
order.setStatus(targetStatus);
}
密封类方案(密封类 + 模式匹配switch)
// 1. 定义密封类OrderStatus,指定4个子类
sealed class OrderStatus permits PendingPaymentStatus, PaidStatus, CancelledStatus, CompletedStatus {
// 省略字段、构造器、getter
// 抽象方法:判断状态流转是否合法
public abstract boolean isTransitionAllowed(OrderStatus targetStatus);
// 抽象方法:处理状态变更
public abstract void handleStatusChange(Order order);
}
// 2. 实现各状态子类(每个子类封装自己的流转规则和业务逻辑)
final class PendingPaymentStatus extends OrderStatus {
public PendingPaymentStatus() {
super("PENDING", "待支付");
}
@Override
public boolean isTransitionAllowed(OrderStatus targetStatus) {
// 待支付可流转到已支付或已取消
return targetStatus instanceof PaidStatus || targetStatus instanceof CancelledStatus;
}
@Override
public void handleStatusChange(Order order) {
// 待支付状态逻辑:设置支付截止时间、发送提醒
order.setPaymentDeadline(LocalDateTime.now().plusHours(2));
notificationService.sendPaymentReminder(order.getUserId(), order.getOrderId());
}
}
final class PaidStatus extends OrderStatus {
public PaidStatus() {
super("PAID", "已支付");
}
@Override
public boolean isTransitionAllowed(OrderStatus targetStatus) {
// 已支付仅可流转到已完成
return targetStatus instanceof CompletedStatus;
}
@Override
public void handleStatusChange(Order order) {
// 已支付状态逻辑:记录支付时间、扣减库存
order.setPaidTime(LocalDateTime.now());
inventoryService.deductStock(order.getOrderItems());
}
}
final class CancelledStatus extends OrderStatus {
public CancelledStatus() {
super("CANCELLED", "已取消");
}
@Override
public boolean isTransitionAllowed(OrderStatus targetStatus) {
// 已取消不可流转
return false;
}
@Override
public void handleStatusChange(Order order) {
// 已取消状态逻辑:记录取消原因、恢复库存
order.setCancelledTime(LocalDateTime.now());
inventoryService.restoreStock(order.getOrderItems());
}
}
final class CompletedStatus extends OrderStatus {
public CompletedStatus() {
super("COMPLETED", "已完成");
}
@Override
public boolean isTransitionAllowed(OrderStatus targetStatus) {
// 已完成不可流转
return false;
}
@Override
public void handleStatusChange(Order order) {
// 已完成状态逻辑:记录完成时间、发送通知
order.setCompletedTime(LocalDateTime.now());
notificationService.sendCompletionNotice(order.getUserId(), order.getOrderId());
}
}
// 3. 使用密封类处理订单状态(结合模式匹配switch)
public void changeOrderStatus(Order order, OrderStatus targetStatus) {
OrderStatus currentStatus = order.getStatus();
// 判断流转是否合法
if (!currentStatus.isTransitionAllowed(targetStatus)) {
throw new IllegalArgumentException(
"非法状态流转:" + currentStatus.getStatusCode() + "→" + targetStatus.getStatusCode()
);
}
// 处理状态变更(编译期校验覆盖所有状态)
switch (targetStatus) {
case PendingPaymentStatus pendingStatus -> pendingStatus.handleStatusChange(order);
case PaidStatus paidStatus -> paidStatus.handleStatusChange(order);
case CancelledStatus cancelledStatus -> cancelledStatus.handleStatusChange(order);
case CompletedStatus completedStatus -> completedStatus.handleStatusChange(order);
}
// 更新订单状态
order.setStatus(targetStatus);
}
方案对比:
- 传统枚举方案:状态逻辑集中在枚举类中,臃肿且难以扩展;新增状态需修改枚举类,违反 “开闭原则”;
- 密封类方案:每个状态的逻辑封装在独立子类中,符合 “单一职责原则”;新增状态只需新增子类并在permits中添加,无需修改原有代码;模式匹配switch保证类型覆盖,无遗漏风险。
3.2 场景 2:支付方式管理(密封接口场景)
需求:管理支付方式,仅允许 “支付宝”“微信支付”“银联支付” 3 种方式,每种方式需实现不同的手续费计算逻辑和支付 API 调用逻辑;支持统一调用支付方法和计算手续费,无需关注具体支付方式。
传统方案(接口 +instanceof判断)
// 传统方案:接口定义支付方法,instanceof判断具体实现
public interface PaymentMethod {
BigDecimal calculateFee(BigDecimal amount);
boolean processPayment(Order order);
}
// 实现类:支付宝、微信支付、银联支付(无限制,可随意新增)
public class AlipayPayment implements PaymentMethod { /* 实现逻辑 */ }
public class WechatPayment implements PaymentMethod { /* 实现逻辑 */ }
public class UnionPayPayment implements PaymentMethod { /* 实现逻辑 */ }
public class CashPayment implements PaymentMethod { /* 错误实现,无手续费 */ }
// 使用接口处理支付(需instanceof判断,存在遗漏风险)
public void processOrderPayment(Order order, PaymentMethod paymentMethod) {
// 计算手续费(需instanceof判断具体类型)
BigDecimal fee;
if (paymentMethod instanceof AlipayPayment) {
fee = ((AlipayPayment) paymentMethod).calculateFee(order.getAmount());
} else if (paymentMethod instanceof WechatPayment) {
fee = ((WechatPayment) paymentMethod).calculateFee(order.getAmount());
} else if (paymentMethod instanceof UnionPayPayment) {
fee = ((UnionPayPayment) paymentMethod).calculateFee(order.getAmount());
} else {
throw new IllegalArgumentException("不支持的支付方式");
}
// 处理支付
boolean success = paymentMethod.processPayment(order);
if (success) {
order.setPaymentFee(fee);
order.setPaymentStatus("PAID");
} else {
throw new RuntimeException("支付失败");
}
}
密封接口方案(密封接口 + 模式匹配switch)
// 1. 定义密封接口PaymentMethod,指定3个实现类
sealed interface PaymentMethod permits AlipayPayment, WechatPayment, UnionPayPayment {
BigDecimal calculateFee(BigDecimal amount);
boolean processPayment(Order order);
}
// 2. 实现各支付方式(final,不可再继承)
final class AlipayPayment implements PaymentMethod {
@Override
public BigDecimal calculateFee(BigDecimal amount) {
// 支付宝手续费逻辑
return amount.multiply(new BigDecimal("0.006")).max(BigDecimal.ONE);
}
@Override
public boolean processPayment(Order order) {
// 调用支付宝API
return alipayClient.pay(order.getOrderId(), order.getAmount()).isSuccess();
}
}
final class WechatPayment implements PaymentMethod {
@Override
public BigDecimal calculateFee(BigDecimal amount) {
// 微信支付手续费逻辑
return amount.multiply(new BigDecimal("0.006"));
}
@Override
public boolean processPayment(Order order) {
// 调用微信支付API
return wxPayClient.pay(order.getOrderId(), order.getAmount()).isSuccess();
}
}
final class UnionPayPayment implements PaymentMethod {
@Override
public BigDecimal calculateFee(BigDecimal amount) {
// 银联支付手续费逻辑
return amount.multiply(new BigDecimal("0.005")).max(new BigDecimal("0.5"));
}
@Override
public boolean processPayment(Order order) {
// 调用银联支付API
return unionPayClient.pay(order.getOrderId(), order.getAmount()).isSuccess();
}
}
// 3. 使用密封接口处理支付(模式匹配switch,无遗漏风险)
public void processOrderPayment(Order order, PaymentMethod paymentMethod) {
// 计算手续费(编译期校验覆盖所有支付方式)
BigDecimal fee = switch (paymentMethod) {
case AlipayPayment alipay -> alipay.calculateFee(order.getAmount());
case WechatPayment wechat -> wechat.calculateFee(order.getAmount());
case UnionPayPayment unionPay -> unionPay.calculateFee(order.getAmount());
};
// 处理支付
boolean success = paymentMethod.processPayment(order);
if (success) {
order.setPaymentFee(fee);
order.setPaymentStatus("PAID");
} else {
throw new RuntimeException("支付失败:" + paymentMethod.getClass().getSimpleName());
}
}
方案对比:
- 传统接口方案:接口实现类无限制,易出现非法实现;计算手续费需instanceof判断,存在遗漏风险;
- 密封接口方案:仅允许指定的 3 个实现类,禁止非法实现;模式匹配switch保证覆盖所有支付方式,无遗漏风险;代码更简洁,无需手动类型转换。
3.3 场景 3:数据类型校验(密封类与记录类结合)
需求:校验用户输入的数据类型,支持 “字符串”“数字”“布尔值” 3 种类型,每种类型需实现不同的校验逻辑(如字符串长度校验、数字范围校验、布尔值格式校验);返回校验结果(成功 / 失败)和错误信息。
密封类 + 记录类方案(Java 16 + 记录类)
// 1. 定义密封类DataType,指定3个子类(使用记录类,简化代码)
sealed class DataType permits StringType, NumberType, BooleanType {
// 抽象方法:校验数据
public abstract ValidationResult validate(String input);
}
// 2. 实现各数据类型(使用记录类,自动生成构造器、getter、equals、hashCode)
// 字符串类型:需指定最小长度和最大长度
final record StringType(int minLength, int maxLength) extends DataType {
@Override
public ValidationResult validate(String input) {
if (input == null) {
return new ValidationResult(false, "字符串不能为空");
}
if (input.length() < minLength) {
return new ValidationResult(false, "字符串长度不能小于" + minLength);
}
if (input.length() > maxLength) {
return new ValidationResult(false, "字符串长度不能大于" + maxLength);
}
return new ValidationResult(true, "校验通过");
}
}
// 数字类型:需指定最小值和最大值
final record NumberType(double minValue, double maxValue) extends DataType {
@Override
public ValidationResult validate(String input) {
if (input == null) {
return new ValidationResult(false, "数字不能为空");
}
try {
double value = Double.parseDouble(input);
if (value < minValue) {
return new ValidationResult(false, "数字不能小于" + minValue);
}
if (value > maxValue) {
return new ValidationResult(false, "数字不能大于" + maxValue);
}
return new ValidationResult(true, "校验通过");
} catch (NumberFormatException e) {
return new ValidationResult(false, "输入不是有效的数字");
}
}
}
// 布尔值类型:仅允许"true"或"false"(不区分大小写)
final record BooleanType() extends DataType {
@Override
public ValidationResult validate(String input) {
if (input == null) {
return new ValidationResult(false, "布尔值不能为空");
}
if (!input.equalsIgnoreCase("true") && !input.equalsIgnoreCase("false")) {
return new ValidationResult(false, "布尔值必须是true或false");
}
return new ValidationResult(true, "校验通过");
}
}
// 3. 校验结果记录类
final record ValidationResult(boolean success, String message) {}
// 4. 使用密封类校验数据
public ValidationResult validateInput(String input, DataType dataType) {
// 模式匹配switch,覆盖所有数据类型
return switch (dataType) {
case StringType stringType -> stringType.validate(input);
case NumberType numberType -> numberType.validate(input);
case BooleanType booleanType -> booleanType.validate(input);
};
}
// 测试代码
public static void main(String[] args) {
// 校验字符串(长度2-10)
DataType stringType = new StringType(2, 10);
ValidationResult stringResult = validateInput("hello", stringType);
System.out.println("字符串校验:" + stringResult.success() + "," + stringResult.message()); // 成功
// 校验数字(1-100)
DataType numberType = new NumberType(1, 100);
ValidationResult numberResult = validateInput("123", numberType);
System.out.println("数字校验:" + numberResult.success() + "," + numberResult.message()); // 失败(超出范围)
// 校验布尔值
DataType booleanType = new BooleanType();
ValidationResult booleanResult = validateInput("TRUE", booleanType);
System.out.println("布尔值校验:" + booleanResult.success() + "," + booleanResult.message()); // </doubaocanvas>
