Java 14预览版Records特性:如何突破单文件公共类限制?
你提的这个需求太贴合实际了——用Records来建模AST的时候,每个节点类型都短小精悍,单独建一个文件确实有点冗余,完全没必要。不过Java的单文件只能包含一个顶级public类/record的规则确实会卡住这个场景,下面给你几个实用的解决方案:
方案1:改为包私有(去掉public修饰符)
如果你的AST相关代码都在同一个包下使用,最简单的办法就是把所有Record的public修饰符去掉,让它们变成包私有。这样既可以把所有代码放在一个文件里,同一包内的其他类也能正常访问这些Record。
修改后的代码示例:
package com.company; public interface Expression { } record IntExp(int value) implements Expression { } record AddExp(Expression left, Expression right) implements Expression { } record SubtractExp(Expression left, Expression right) implements Expression { } // 其他AST节点...
这种方式最简洁,完全不需要额外结构,适合内部包内使用的场景。
方案2:嵌套在公共类/接口中
如果需要这些Record对外暴露(比如跨包使用),可以把它们嵌套在一个公共的顶级类或者接口里面,作为内部public record。这样外部代码可以通过Expression.IntExp这样的路径访问,同时所有代码都集中在一个文件里。
示例代码:
package com.company; public interface Expression { public record IntExp(int value) implements Expression { } public record AddExp(Expression left, Expression right) implements Expression { } public record SubtractExp(Expression left, Expression right) implements Expression { } // 其他AST节点... }
这种方式既保持了公共访问性,又满足了代码集中的需求,非常适合对外提供AST模型的场景。
方案3:结合密封类(Java 16+)
如果你的项目已经升级到Java 16或更高版本,推荐用**密封类(Sealed Classes)**来配合Records建模AST。密封类可以明确限制哪些类可以实现它,完美契合AST节点的封闭性,同时你可以把所有实现类(Records)放在同一个文件里,无论是作为包私有还是嵌套成员都可以。
示例代码:
package com.company; public sealed interface Expression permits IntExp, AddExp, SubtractExp { } public record IntExp(int value) implements Expression { } public record AddExp(Expression left, Expression right) implements Expression { } public record SubtractExp(Expression left, Expression right) implements Expression { } // 其他AST节点...
(注:这里如果把Records改成包私有,也可以放在同一个文件里,密封类的permits列表依然能识别它们)
为什么原代码无法编译?
Java的核心规则是:每个顶级public类/record必须放在与其同名的.java文件中。你原来的代码里有多个顶级public record,它们各自需要对应的IntExp.java、AddExp.java等文件,所以编译器会报错。上面的方案都是通过规避“顶级public”这个限制来实现代码集中的。
内容的提问来源于stack exchange,提问作者james lessy

