在构建领域模型或框架核心类型时,我们常面临两难:把类写成普通可继承的,后期会出现大量不可控的子类,破坏模式匹配与逻辑完备性;把类彻底封死,又没法在业务模块里做轻量定制。Java 17正式引入的sealed与non-sealed正好用来解决这种局部弹性问题。

sealed与non-sealed的基本语义
sealed类通过permits关键字声明允许直接继承它的子类名单,编译器会强制校验:任何不在名单里的类型都不能继承它,且子类必须处于同一模块或同一包下。这样根类型的所有直接后代在编译期就是确定的,非常适合做代数数据类型或状态机。
但sealed只约束“直接子类”。如果一个被许可的子类自身标为non-sealed,那么它又可以像普通类一样被任意外部类型继承。这就形成了一棵“根部收紧、枝节放开”的树:核心分支被严格审批,某个特定分支下的延伸则交给业务自由发挥,不会污染整体体系。
定义受控根类型与局部开放子类
下面示例模拟支付渠道模型:根类PaymentChannel只允许三个直接子类,其中ThirdPartyChannel标为non-sealed,以便各接入方扩展自己的实现。
// 根类型严格限定直接子类
public abstract sealed class PaymentChannel
permits BankChannel, InternalChannel, ThirdPartyChannel {
public abstract String getChannelId();
}
// 完全封死,不允许任何延伸
public final class BankChannel extends PaymentChannel {
public String getChannelId() { return "bank"; }
}
// 完全封死
public final class InternalChannel extends PaymentChannel {
public String getChannelId() { return "internal"; }
}
// 非密封:允许外部继续继承
public non-sealed class ThirdPartyChannel extends PaymentChannel {
public String getChannelId() { return "third-party"; }
}
// 业务方在别的包里自由扩展,但不影响根类型管控
public class AlipayChannel extends ThirdPartyChannel {
public String getChannelId() { return "alipay"; }
}
上述代码中,BankChannel与InternalChannel用final彻底锁死,保证关键路径不会被误改;ThirdPartyChannel以non-sealed放开,使接入层可以按需增加具体渠道,而根类型依然只认识三个直接孩子,模式匹配时仍能穷举。
这种写法比单纯用接口加文档约定要可靠得多。编译器会阻止有人偷偷写class Foo extends PaymentChannel,却拦不住ThirdPartyChannel的合理派生,审查成本大幅下降。
在严控体系中保留弹性的实践要点
首先,根类型应只放真正稳定的抽象,不要因为“可能有人要改”就把方法留成空。被non-sealed放开的子类,最好也提供默认实现或模板方法,避免下游写出行为割裂的子类。
其次,如果旧系统已从开放继承迁移过来,可以先将根类标为sealed并permits原有子类,再把其中需要扩展的那几个改为non-sealed。这样不需要一次性重构所有调用点,也能逐步收紧类型边界。
编译期与运行期行为对比
下表列出不同修饰下的继承约束,帮助团队在评审时快速判断该用哪种粒度。
| 父类修饰 | 直接子类要求 | 子类可否再被继承 | 适用场景 |
|---|---|---|---|
| 普通类 | 无限制 | 默认可继承 | 无管控诉求的工具类 |
| sealed | 必须permits且同包/模块 | 子类自行决定 | 核心模型、状态枚举 |
| sealed + final子类 | 名单内 | 不可 | 完全固定的分支 |
| sealed + non-sealed子类 | 名单内 | 可自由继承 | 核心受控、局部扩展 |
从运行期看,non-sealed子类与普通类性能无异,JVM不会因继承深度增加额外检查。真正收益在编译期和代码评审:类型层级可读、模式匹配可穷举、误用能在写代码时暴露。
与接口和枚举的取舍
有人会问,用接口加默认方法是不是也能留弹性?接口确实灵活,但接口无法限制“谁来实现”,也不能像sealed那样在permits里白纸黑字列出后代。枚举则完全封闭,连non-sealed这种半开都做不到。
因此当你的领域对象既是数据又是有限状态,且只有某一类状态需要外接定制时,sealed加non-sealed子类是当前Java里最贴合的表达方式。它让体系在“严”与“活”之间有了精确的刻度,而不是非黑即白。
sealednon-sealedJava继承控制修改时间:2026-08-08 09:06:27