【问题标题】:JDK 16 Records, Constructors and Additions Over Time?随着时间的推移,JDK 16 记录、构造函数和添加?
【发布时间】:2021-06-04 01:23:02
【问题描述】:

我对使用新的 JDK 16 记录很感兴趣,但是当我尝试创建一个具有附加参数的新记录时,我收到一个错误:

public record DrivePacket(Path drivePath, long driveSize) {
        public DrivePacket(Path drivePath, long driveSize) {
            this.drivePath = drivePath;
            this.driveSize = driveSize;
        }
        public DrivePacket(Path drivePath, long driveSize, String ID) {
            this(drivePath, driveSize);
            this.id = id;
        }
}

是否只针对没有任何可变性的对象进行记录,或者它们可以随着时间的推移而扩展?

我对这种类型的类有一个新要求(特别是意外添加了通过 NFS 共享的驱动器类型“远程”驱动器。

我发现自己试图想出一些方案,比如使用driveSize 来表示“远程”状态,但这确实是开发枚举的原因。

在这种情况下我该怎么办?我可以向构造函数添加选项并将它们存储在私有最终字段中吗?或者我应该做一个DrivePacket的子类 -> RemoteDrivePacket,还是应该为RemoteDrivePacket做一个标记接口?

开发人员打算使用的典型方式是什么?

【问题讨论】:

  • docs.oracle.com/en/java/javase/16/language/records.html 您已将记录定义为具有两个属性(drivePathdriveSize),您不能转而向其添加任意属性。记录类不能被子类化;它们隐含为final。它们可以扩展,但您尝试在错误的级别进行扩展。
  • 记录是根据特定目的设计的。在您的情况下,如果您将记录定义为具有 ID 并且有一个 ctor 调用带有虚拟 ID(或其他)的规范 ctor,那么它就可以了。您也可以使用静态工厂,尽管这里似乎没有必要。我真的没有在这里看到故障点或设计疏忽。它们被明确设计为轻量级数据载体类。
  • 如果您的应用程序需要类的更广泛的功能......例如子类化,以便您的设计的这方面可以在不重新编码的情况下发展......使用常规类。是的 - 您确实需要尽早做出正确的决定以预测未来的需求。但这适用于所有方面。
  • 阅读JEP 395 了解有关记录的所有信息、发明的原因以及特征和限制。
  • 我还要补充一点,即使使用常规类,也不会按照示例中显示的方式完成,其中任意成员属性(非常规命名的ID)试图成为设置——您通常会添加成员属性,就像使用记录一样。

标签: java java-16


【解决方案1】:

如果有合理的默认值,则可以兼容地添加组件到记录中,并且在设计此功能时考虑了这一点。考虑:

record Point(int x, int y) { }

并且您想添加一个z 组件,其中零是合理的默认值。如果你只是一味地改成

record Point(int x, int y, int z) { }

这将与现有客户端不兼容源代码或二进制文件,但您可以通过为旧状态描述提供替代构造函数来解决此问题:

record Point(int x, int y, int z) { 
    public Point(int x, int y) { this(x, y, 0); }
}

现在,旧的构造函数调用(二进制和源代码)和旧的序列化实例(如果默认值与序列化将用于序列化流中不存在的字段的零默认值相同)将按预期工作。但是,它与 renamereorderremove 组件不兼容。

【讨论】:

  • 你不能通过声明另一个构造函数来安全地删除组件,而现在删除的参数只是被忽略了吗? public Point(int x, int y, int z) { this(x, y); }
  • @MattLeidholm 可能还有现有的客户端调用已删除组件的访问器;您还需要使用始终返回(在旧规则下有效)默认值的显式访问器来伪造这些人。但即使这样,风险也更大。
【解决方案2】:

它们不能被子类化,也不能具有“单独添加一个”意义上的字段,但是您可以简单地在其中扔一个新属性; public record DrivePacket(Path drivePath, long driveSize, String id) {} 工作正常。

当然,现在所有创建新 DrivePacket 对象的代码都需要更新。您应该能够添加一个自定义构造函数,例如填写一些默认值。

如果您希望它们可构建、可扩展、具有非最终字段等,请查看 lombok's @Value。 (免责声明:我确实在龙目岛工作)。

--编辑--

我想我会在此处添加该构造函数,以展示如何确保调用 new DrivePacket(path, size)(无 id)的“旧”代码可以正常工作:

public record DrivePacket(Path drivePath, long driveSize, String id) {
    public DrivePacket(Path drivePath, long driveSize) {
        this(drivePath, driveSize, "");
    }
}

“完整的”全 3-of-em 构造函数存在,这定义了第二个构造函数,确保任何刚刚进入 new DrivePacket(path, size) 的代码都获得 "" 的 ID。所有 DrivePackets 都有一个 ID(你不能让一半拥有它而另一半没有;那么“DrivePacket”将不再描述单一类型的概念,而不是 java 的工作方式),现在旧式 DrivePacket 对象有一个ID 的空字符串。

【讨论】:

    猜你喜欢
    • 2012-04-15
    • 2012-03-09
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-01
    相关资源
    最近更新 更多