【问题标题】:Java records and the single class per file limitJava 记录和每个文件的单个类限制
【发布时间】:2021-03-20 20:00:35
【问题描述】:

我正在试用 java 14 的新记录功能,目前处于预览阶段。
我知道在 Java 中,每个文件必须有一个公共类,但是新的记录语法非常好和短,拥有一堆文件,每个文件都有一行代码似乎很浪费。

我特别想尝试像这样对简单的 AST 进行建模,并且我认为将所有内容放在一个文件中确实可以提高可读性和理解力。

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 {
}

// Etc..

但不幸的是,这不会编译。
所以我的问题是:

有什么办法可以绕过这个限制,或者有什么办法可以将这样的代码全部保存在一个地方吗?

【问题讨论】:

  • “将这样的代码保存在一个地方” ...这就是包的用途。
  • "我认为将所有内容放在一个文件中确实可以提高可读性和理解力" FWIW,我不知道。任何设计良好的接口的关键在于它的潜在实现是无限的。仅仅因为您在一个文件中有 3 个实现并不意味着我可以忽略可能存在其他实现的事实。另外,我假设您的接口和实现实际上至少有一个带有实现的方法,所以只有 4 行定义的情况是相当人为的。
  • @Michael 你应该阅读sealed types
  • @Michael 对不起,我不是故意嘲笑你的。这句话:“任何设计良好的接口的关键在于它的潜在实现是无限的”让我觉得,也许,你没有读过密封类型。我的观点是密封类型似乎是 OP 所要求的
  • @fps 是的,密封类型似乎是我正在寻找的!事实上,我基本上是在尝试从函数式语言中重新创建 sum 类型。如果你把它放在答案中,我会接受它。

标签: java java-14 java-record


【解决方案1】:

内部课程以及sealed types 似乎是您所追求的。 Java 15 引入了密封类型作为预览功能。

您曾说过您希望在一个文件中对 AST 进行建模。 AST 是详尽的。而这种穷举可以用内部类密封类型来建模。

在您的特定示例中,如果您在同一编译单元中定义超类型及其所有允许子类型(子类型为内部类),编译器将自动推断permits给你:

public sealed interface Expression {

    record IntExp(int value) implements Expression { }

    record AddExp(Expression left, Expression right) implements Expression { }

    record SubtractExp(Expression left, Expression right) implements Expression { }
}

在这里,编译器会自动将IntExpAddExpSubtractExp 推断为permits,并且不允许任何其他类型(在任何其他编译单元中声明)既不扩展也不实现Expression界面。

【讨论】:

  • 密封类与 OP 的要求无关
  • “因为编译器不允许编译单元之外的实现类”,但这不是 OP 的要求。他们只想在一个文件中包含几个公共类。我们可以假设在其他地方不会有任何其他实现,但是OP没有说明。
  • @Tom OP 希望在一个文件中包含 AST。 AST 是详尽的。并且这种详尽性可以用内部类密封类型建模
  • @ZhekaKozlov 哇,你有没有看错那个:) 这里的回答者清楚地辨别出真正被问到的问题,OP 甚至说“这就是我要找的”。因此,也许您需要将“这不是所要求的”过滤器调整几个档次。
  • 为了它的价值,我们花了相当多的时间来处理 OP 在设计密封类时提出的潜在问题,因为这个问题在定义一个紧密相关的组时最常出现数据载体类,例如 AST 的节点。这个用例——记录嵌套在接口内——通过permits列表的推断得到回报,如果你将它们分别放在一个文件中,你就不会得到。有一次我们考虑放宽对辅助类的限制,但发现没有必要,因为这个成语可以解决问题。
【解决方案2】:

一种明显的方法是将“预定义”记录类移动到接口中:

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 {
    }
}

【讨论】:

  • 值得指出的是,如果(例如)你有一个特别复杂的表达式类型,你仍然可以有单独的源文件来实现接口。
猜你喜欢
  • 1970-01-01
  • 2016-09-05
  • 2015-08-14
  • 2020-06-19
  • 1970-01-01
  • 1970-01-01
  • 2013-10-21
  • 2015-07-26
  • 2015-11-05
相关资源
最近更新 更多