【问题标题】:Why do Java Records have accessor methods instead of public final fields?为什么 Java Records 有访问器方法而不是公共的 final 字段?
【发布时间】:2021-03-19 03:40:49
【问题描述】:

JEP-395

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

  • 对于标头中的每个组件,有两个成员:一个与组件具有相同名称和返回类型的公共访问器方法,以及一个具有相同名称的私有最终字段类型为组件;

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


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

因此更改 API 内部实现(记录组件)不是一个合适的理由。但我有充分的理由来自@Brian Goetz

谢谢大家关注我的傻问题

【问题讨论】:

  • 直接访问你的字段是没有意义的(编程封装的基本原理),所以它只是一个很好的编程实践
  • 如果有公共访问器方法,那么公开该字段的意义何在?我无法理解您的第二个问题。
  • 几十年的经验不同意你的看法。
  • 而在这几十年的 Java 历史中,并没有纯数据类。
  • @DonggiKim Java 中一直存在纯数据类,您只需要自己处理所有管道,而不是编译器为您完成大部分工作。

标签: java java-16


【解决方案1】:

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

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

引用cmets中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 Goetzthis answer 上的 cmets 中提供了另一个原因:

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

【讨论】:

  • 不覆盖访问器会使方法违反直觉吗?它会使程序员对记录的访问者有不一致的假设。为什么与 final 字段同名的访问器可以做到这一点?
  • @DonggiKim 为什么会这样?事实访问器是方法,它比直接字段访问的直接夹克为您提供了更多的灵活性。您不必使用可以显式实现访问器的事实(在大多数情况下您可能不应该使用它),但如果您需要,您可以。你也可以用它来进化记录,例如,添加一个之前计算过的字段,或者将曾经是单个字段的东西拆分为多个字段,然后添加一个方法来替换访问器,确保二进制兼容性,等
  • @DonggiKim 封装的动机与其他非记录类相同。例如,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 谢谢大家关注我的愚蠢问题
  • @DonggiKim 这是一个很好的理由,我什至没有想到。我在我的答案中添加了该评论的引用。
【解决方案2】:

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

public record MyRecord(String myProperty) {
    @Override
    public String myProperty() {
        return "The property is " + myProperty;
    }
}

这样的事情对于公共的 final 字段是不可能的。

【讨论】:

  • 不可变的数据类是否应该有这样的方法(行为)?我不认为介绍记录只是为了添加一些语法糖。
  • 如果没有覆盖访问器的能力,记录将无法正确支持可变对象(例如数组)作为组件。如果您有一个数组组件,您可能希望在构造函数和访问器中执行防御性复制(这还需要覆盖equals,以保留Record 的规范);如果它是一个公共的 final 字段,则无法封装组件的可变性。
  • 谢谢@Brian Goetz!现在我得到了现实的例子,并且理解了
【解决方案3】:

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

【讨论】:

  • 但是不可变数据类难道不是和普通类有不同的用途吗?
  • 在大多数情况下,使用 getter 从 POJO 迁移到 Records 破坏依赖代码,因为 Record 访问器的格式为 myProperty() 而不是 getMyProperty()
  • @DonggiKim:不。很多——甚至可能是大多数——你的类应该已经是不可变的数据保存类了。
  • 我有充分的理由来自@Brian Goetz 谢谢大家关注我的愚蠢问题
猜你喜欢
  • 2017-02-02
  • 1970-01-01
  • 2012-12-26
  • 2018-11-12
  • 2014-06-06
  • 2012-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多