non-sealed类必须显式列出允许继承的子类,即通过permits子句声明所有合法直接子类,否则编译报错;子类自身也需用final/sealed/non-sealed明确继承策略。

non-sealed 类必须显式列出允许继承的子类
Java 17+ 的 non-sealed 不是“默认开放”,而是“选择性开放”——它要求你在父类中明确写出哪些子类被允许继承它。如果你只写 non-sealed class A 却不指定 permits B, C,编译器会直接报错:error: non-sealed class must specify permits clause。
这是最容易卡住的地方:很多人以为 non-sealed 是“松开密封”,实际它是“白名单机制”。你得在父类定义里同步写清所有合法直接子类:
non-sealed class Shape permits Circle, Rectangle, Triangle { }
之后,Circle、Rectangle、Triangle 才能合法声明为 final、sealed 或继续用 non-sealed 向下传递。
子类必须用 sealed/non-sealed/final 显式响应 permits 约束
父类写了 permits B, C,不代表 B 和 C 就能自由继承——它们自己也必须声明继承策略,否则编译失败:
-
final class Circle extends Shape✅ 允许,且封死继承链 -
sealed class Rectangle extends Shape permits RoundedRect✅ 允许,但需继续列明下级许可 -
non-sealed class Triangle extends Shape✅ 允许,表示向更下层开放(但注意:它仍必须出现在父类的permits列表中) -
class UnknownShape extends Shape❌ 编译错误:UnknownShape is not allowed to extend sealed class Shape
non-sealed 子类可被任意包中的类继承,但仅限直接继承
一个 non-sealed 类(比如 Triangle)一旦被允许继承,任何包里的类只要写 class MyTri extends Triangle 就能通过编译——它不检查包可见性,也不需要模块声明。但这只适用于**直接继承**:
- 如果
Triangle是non-sealed,那么MyTri可以继承它 - 但如果
MyTri想再被别人继承,它自己也得声明策略(final/sealed/non-sealed),否则默认是final(Java 15+ 默认类非sealed,但不等于可被继承;只有显式non-sealed才开放) - 跨模块时,还需确保模块
exports对应包,否则即使策略允许,也会因访问控制失败
和 sealed / final 混用时,permits 列表必须完整且无歧义
常见误操作是遗漏子类或拼写不一致。比如父类写 permits Circle, Rect,子类却叫 Rectangle,就会触发 class Rectangle is not listed in permits clause。另外注意:
-
permits列表中的类名必须是**简单名**(如Circle),不能带包路径 - 所有被
permits的类必须与父类在同一个模块(或未命名模块),否则编译器无法解析 - 不能用通配符或正则,也不能用变量或条件表达式动态控制——
permits是编译期静态约束 - IDE(如 IntelliJ)有时不会实时高亮拼写错误,建议每次修改后手动编译验证
真正难的不是语法,而是设计时想清楚哪一层该开放、哪一层该收敛。一旦 permits 写错,整个继承链就断在编译期,而且错误提示往往指向子类而非父类的 permits 拼写问题。










