vab*_*048 7 java java-record java-16
我有一种情况,我希望特定类型的记录实例只能使用同一包内单独类中的工厂方法创建。这样做的原因是因为在创建记录之前我需要执行大量的验证。
记录旨在成为其验证字段的哑数据载体,但验证不能在记录的构造函数中进行,因为我们需要访问一些精心设计的验证器对象才能实际执行验证。
由于将验证器对象传递给记录构造函数意味着它们将构成记录状态的一部分,这意味着我们不能使用记录构造函数来执行记录的验证。
因此,我将验证提取到它自己的工厂中并编写了如下代码(工厂类和同一包中的记录):
package some.package;
// imports.....
@Component
class SomeRecordFactory {
private final SomeValidator someValidator;
private final SomeOtherValidator someOtherValidator;
// Rest of the fields
// ....
// constructor
// ....
public SomeRecord create(...) {
someValidator.validate(....);
someOtherValidator.validate(....);
// .... other validation
return new SomeRecord(...);
}
}
Run Code Online (Sandbox Code Playgroud)
package some.package;
public record SomeRecord(...) {
/* package-private */ SomeRecord {
}
}
Run Code Online (Sandbox Code Playgroud)
无论出于何种原因,上述内容不适用于 IntelliJ 抱怨:
Compact constructor access level cannot be more restrictive than the record access level (public)
Run Code Online (Sandbox Code Playgroud)
我可以通过使用普通类(允许使用单个包私有构造函数)来避免这个问题,但希望更准确地将数据建模为记录。
为什么记录存在此限制?未来是否有取消此限制的计划?
vab*_*048 25
我在 amber 邮件列表上提出了这个问题(http://mail.openjdk.java.net/pipermail/amber-dev/2020-December.txt)。
提出了这样的问题:
规范构造函数必须具有与记录相同的访问权限到底是什么原因?
给出的答案是(重点是我的):
记录被命名为元组,它们仅由其组件以透明的方式定义,即没有封装。从元组中,您可以访问每个组件的值,并且可以从所有组件值中创建一个元组。 这个想法是,在一个方法中,如果您能够看到一条记录,您就可以创建它。因此,规范构造函数与记录本身具有相同的可见性。
因此,存在限制是为了符合设计目标和事实,即如果有人拥有记录的实例,他们应该能够解构它,然后使用规范构造函数重建它。当然,作为推论,这需要规范构造函数具有与记录本身相同的访问权限。
为什么记录存在此限制
JEP 或 JLS 中没有明确说明该决定的合理性,但我认为 JEP 的以下摘录暗示了这一点:
因为记录使语义声明成为其数据的透明载体......
“透明载体”意味着(对我而言)记录被设计为具有最小的抽象边界。限制构造函数的访问意味着(对我而言)一个额外的抽象边界。
此外,我怀疑具有更多限制性访问修饰符的记录构造函数可能会阻碍或复杂化 Java 未来版本中记录的预期用例。
无论如何,我的看法是,如果你想要这样的奇特东西,你应该声明一个类而不是一个记录。
未来是否有取消此限制的计划?
我不知道任何。没有关于此的(公共)公开 Java 错误或 RFE。
实际上,与此主题相关的所有 JDK 错误都是为了确保 Java 15+ 规范明确限制。没有迹象表明限制是偶然或疏忽造成的。
您不能在这里完全按照您想要的方式进行操作 -public使用隐藏构造函数进行记录(或者更一般地说 - 其构造函数具有更严格的访问权限的记录 - 正如您已经指出的那样!)。但如果稍微调整一下要求,您就可以实现类似的预期结果。
诀窍是使其record自身隐藏(private或包私有)并仅公开工厂类:
// package-private
record SomeRecord(String key, String value) {}
Run Code Online (Sandbox Code Playgroud)
public final class SomeRecordFactory {
public SomeRecord create(String value) {
return new SomeRecord("key", value);
}
}
Run Code Online (Sandbox Code Playgroud)
然后你被迫创建如下实例:
SomeRecordFactory factory = new SomeRecordFactory();
var myObject = factory.create("my value");
Run Code Online (Sandbox Code Playgroud)
注意var- 我不能使用,因为MyRecord它是隐藏的(在这种情况下是包私有的,可以private通过将其嵌套在工厂类中来实现)。
如果您想公开类型(例如,在无法使用var方法/构造函数参数的情况下),您可以提供sealed interface表示类型和仅permit隐藏记录的类型:
public sealed interface SomeType permits SomeRecord {
String key();
String value();
}
Run Code Online (Sandbox Code Playgroud)
// package-private
record SomeRecord(String key, String value) implements SomeType {}
Run Code Online (Sandbox Code Playgroud)
public final class SomeRecordFactory {
public SomeType create(String value) {
return new SomeRecord("key", value);
}
}
Run Code Online (Sandbox Code Playgroud)
然后你可以创建实例而不var需要像:
SomeRecordFactory factory = new SomeRecordFactory();
SomeType myObject = factory.create("my value");
Run Code Online (Sandbox Code Playgroud)
这种方法的缺点是您无法访问记录。所以你不能使用像模式匹配这样的新功能(详尽的开启sealed interface+record解构)。您必须在您的包中执行此操作(您可能会认为这是一件好事,因为它将对您的 API 用户隐藏!)。
| 归档时间: |
|
| 查看次数: |
523 次 |
| 最近记录: |