为什么 Java Records 有访问器方法而不是公共 final 字段?

Don*_*Kim -4 java java-16

JEP-395

一个记录类自动获取许多标准成员:

  • 对于头部中的每个组件,两个成员:一个组件同名且返回类型相同公共访问器方法,以及一个与组件类型相同的私有 final 字段

如果生成的 final 字段与访问器方法具有相同的名称,为什么不生成公共 final 字段呢?


由于记录类的实例可以序列化和反序列化,因此记录组件几乎不会被小心地更改。但是,不能通过提供 writeObject、readObject、readObjectNoData、writeExternal 或 readExternal 方法来自定义流程

所以改变 API 内部实现(记录组件)不是一个合适的理由。但我从@Brian Goetz那里得到了充分的理由

谢谢大家关注我的傻问题

Mor*_*itz 5

我认为这个决定的一个关键因素是你现在有能力覆盖Record 的 getter:

public record MyRecord(String myProperty) {
    @Override
    public String myProperty() {
        return "The property is " + myProperty;
    }
}
Run Code Online (Sandbox Code Playgroud)

对于公共最终字段来说,这样的事情是不可能的。

  • 如果没有重写访问器的能力,记录将无法正确支持可变对象(例如数组)作为组件。如果您有一个数组组件,您可能需要在构造函数和访问器中执行防御性复制(这还需要重写“equals”,以便保留“Record”的规范);如果它是公共最终字段,则无法封装组件的可变性。 (11认同)

Mar*_*eel 5

记录可以实现接口,因此记录的访问器可以是接口方法的实现。此外,使用访问器而不是直接字段访问提供了更大的灵活性,例如,您可以将直接返回字段的访问器替换为以某种方式派生值的访问器(反之亦然)。

记录还允许您覆盖访问器 - 而不是简单地返回字段 - 做一些额外的事情。使记录使用直接字段访问会限制和限制您可以对记录执行的操作,从而限制它们的有用性,而让访问器为您提供直接字段访问提供的基线,并在必要时能够执行更多操作。

引用Holger在评论中提供的示例:

public record R(int a, int b) { public int c() { return …; }}public record R(int a, int c) { public int b() { return …; }}提供相同的 API,无论它们的内部表示如何。

简而言之,为字段生成访问器比直接字段访问提供了更多的灵活性和功能。这同样适用于普通的不可变类。

Brian Goetz对此答案的评论中提供了另一个原因:

如果没有覆盖访问器的能力,记录将无法正确支持可变对象(例如数组)作为组件。如果您有一个数组组件,您可能希望在构造函数和访问器中执行防御性复制(这也需要覆盖 equals,以保留 Record 的规范);如果它是公共 final 字段,则无法封装组件的可变性

  • @DonggiKim 为什么会这样?事实上,访问器是方法,只是为您提供比直接字段访问的紧身衣更大的灵活性。您不必使用可以显式实现访问器的事实(并且在大多数情况下您可能不应该使用它),但如果需要,您可以。您还可以将其用于记录的演变,例如,添加之前计算过的字段,或者将以前是单个字段的内容拆分为多个字段,然后添加一个方法来替换访问器,确保二进制兼容性, ETC。 (2认同)
  • @DonggiKim 封装的动机与其他非记录类相同。例如,类 `public record R(int a, int b) { public int c() { return …; }}` 和 `public record R(int a, int c) { public int b() { return …; }}` 提供相同的 API,无论其内部表示如何。 (2认同)

Lou*_*man 2

通常,最佳实践是使用访问器方法而不是直接字段访问。即使有记录,继续这种做法也是有意义的,以便在不破坏现有代码的情况下重命名字段。