本文深入探讨了 java 密封类(sealed class)在 `permits` 子句中处理泛型类型参数时常见的编译错误。根据 java 语言规范,`permits` 子句仅接受原始类型名称,而非带有泛型参数的类类型。文章通过具体代码示例,阐明了这一语法要求,并解释了 `javac`(maven 使用)与 `ecj`(eclipse/vscode 使用)编译器在执行此规范时的差异,帮助开发者避免潜在的构建失败。
Java 密封类(Sealed Class)是 JDK 17 中引入的一项特性,它允许开发者显式声明哪些类或接口可以扩展或实现一个密封类或接口。这为创建更受控的类层次结构提供了强大的工具,尤其适用于实现代数数据类型(ADT)模式。密封类通过 permits 子句列出其允许的直接子类型。
然而,在实践中,当密封类与泛型结合使用时,开发者可能会遇到编译错误,尤其是在使用不同的 Java 编译器时。一个常见的问题是,在 permits 子句中错误地包含了泛型类型参数。
根据 Java 语言规范(JLS),permits 子句的语法要求是列出“类型名称”(TypeName),而不是“类类型”(ClassType)。这意味着在 permits 列表中,只能指定类的裸名,而不能包含任何泛型类型参数。
例如,如果有一个泛型密封类 APath
错误的示例代码:
考虑以下泛型密封抽象类及其嵌套子类:
public sealed abstract class APathpermits APath .LastWildcard , APath .WholeWildcard { // ... 类成员 ... protected final List dirs; public final class LastWildcard extends APath { // ... } public final class WholeWildcard extends APath { // ... } } public sealed abstract class ADir permits ADir.Wildcard, ADir.Dir { public final class Wildcard extends ADir {} public final class Dir extends ADir {} }
在上述 APath 类的 permits 子句中,APath
当使用 maven-compiler-plugin(默认使用 javac 编译器)进行编译时,会导致类似以下的编译错误:
[ERROR] /D:/Experiment/src/main/java/prefile/pref/APath.java:[13,52] '{' expected [ERROR] /D:/Experiment/src/main/java/prefile/pref/APath.java:[15,25] class, interface, enum, or record expected ...
这些错误通常指向 permits 子句中第一个带有泛型参数的位置,表明编译器无法解析该语法。
要解决此问题,只需从 permits 子句中移除泛型类型参数,只保留原始的类名称。
正确的示例代码:
import java.util.List; public sealed abstract class APathpermits APath.LastWildcard, APath.WholeWildcard { // ... 类成员 ... protected final List dirs; public final class LastWildcard extends APath { // ... } public final class WholeWildcard extends APath { // ... } } public sealed abstract class ADir permits ADir.Wildcard, ADir.Dir { public final class Wildcard extends ADir {} public final class Dir extends ADir {} }
在这个修正后的代码中,permits 子句现在是 permits APath.LastWildcard, APath.WholeWildcard。这里只列出了子类的原始名称,符合 Java 语言规范的要求,因此 javac 编译器将能够成功编译。
值得注意的是,开发者可能会发现在集成开发环境(IDE)如 Eclipse 或 VSCode(使用其 Java 扩展)中,上述带有泛型参数的 permits 子句并没有报错,但使用 Maven 构建时却失败了。这通常是由于 IDE 内部使用的编译器与 Maven 默认使用的编译器不同造成的。
在这种情况下,ecj 可能对 permits 子句中的泛型参数表现出更宽松的解析行为,导致在 IDE 中不报错。然而,由于 javac 严格遵守 JLS,它会正确地将这种用法识别为语法错误。因此,即使 IDE 中没有警告,也应始终以 javac 的编译结果为准,尤其是在进行项目构建和部署时。
通过理解并遵循这些原则,开发者可以有效避免在 Java 密封类与泛型结合使用时可能遇到的编译问题,确保代码的健壮性和可移植性。