为什么 StructuredArray 需要是不可构造的?

Why does StructuredArray need to be non-constructible?

This talk 在 34:00 描述了 Java 的 StructuredArray 的设计。一切都相当清楚,除了以下几点:

它不应该是可构造的,即实例可能只能通过某些 静态工厂方法 获得,例如 newInstance。同时,它们应该是可子类化的,这意味着必须有一个 public 构造函数,并且在运行时将保证不可构造性。这听起来很老套,所以我想知道 为什么?

我了解工厂的一般优势,尤其是静态工厂方法。但是我们在这里得到了什么,以便让黑客可以接受?

语义 浏览 API 文档,我的理解是这主要是语义问题。并提供 Fluent API。此外,如果您转到演示文稿的结论幻灯片,您应该注意到语义项目符号排在第一位(如果我们不计算源代码 url)。

如果我们选择普通数组。它们呈现出清晰的语义:

  • 数组类型
  • 数组长度
  • 元素类型

结果

我们有一个统一的数组处理模型。而 API 是 crystal 清楚的。没有 10 种不同的方式来处理数组。我相信对于 Java 语言开发人员来说,api 的这种简洁性极其重要。强迫不可构造性,他们隐含地强迫我们按照他们希望我们使用它的方式使用 API 。

施工

因为 StructuredArray 本质上也是数组。提供构造函数会立即迫使我们使用 StructuredArray 的具体实现,这会自动产生问题,引入这种 "What exactly is an "Array?".

的统一模型

这就是为什么通过 Java 文档我们可以看到 StructuredArray 的实际构造方式:

  static <S extends StructuredArray<T>,T> S newInstance(java.lang.invoke.MethodHandles.Lookup lookup,
 java.lang.Class<S> arrayClass, 
 java.lang.Class<T> elementClass, 
 java.util.Collection<T> sourceCollection)

这里可见的是 StructuredArray 强制执行几项操作:

  1. 它强制所有客户端 classes 使用 "StructuredArray" 而不是具体实现。
  2. StructuredArray 本质上是不可变的。
  3. 不变性意味着长度有严格的表示法。
  4. 结构化数组有一个元素源。一旦消耗掉就可以处理掉。
  5. 与常规数组类似,结构化数组具有元素类型的概念。

我相信有很强的语义符号,而且作者也给了我们一个很好的提示,告诉我们编码应该如何发生。

结构化数组的另一个有趣的特性是能够传递构造函数。我们再次谈论接口和 API 与实际实现的强解耦。

数组模型

我的话通过检查 StructuredArrayModel 得到进一步证实 http://objectlayout.github.io/ObjectLayout/JavaDoc/index.html?org/ObjectLayout/StructuredArray.html

StructuredArrayModel(java.lang.Class<S> arrayClass, java.lang.Class<T> elementClass, long length)

从构造函数中可以看到三件事: - 数组 class - 元素的类型 - 长度

进一步观察结构化数组支持的结构:

An array of structs: 
struct foo[];

A struct with a struct inside: 
struct foo { int a; bar b; int c; };

A struct with an array at the end: 
struct foo { int len; char[] payload; };

StructuredArrayModel 完全支持它 与 StructuredArray 相比,我们能够轻松实例化模型的具体实现。

StructuredArray 向我们展示了传递伪构造函数的能力 http://objectlayout.github.io/ObjectLayout/JavaDoc/org/ObjectLayout/CtorAndArgs.html

newInstance(CtorAndArgs<S> arrayCtorAndArgs, java.lang.Class<T> elementClass, long length)

StructuredArray class 的要点是有一天它可以被一个内部实现替换,该实现将整个数组(包括组件对象)分配为一个长内存块。 发生这种情况时,对象的大小将取决于元素的数量和元素class

如果 StructuredArray 有一个 public 构造器,那么你可以写 x = new StructuredArray<>(StructuredArray.class, MyElement.class, length)。这似乎没有任何问题,除了在 bytecode 中,这变成了分配对象的 new 指令,然后是 separate invokespecial 指令调用对象的构造函数。

你看到问题了——new指令必须分配对象,但它不能,因为对象的大小取决于构造函数参数(它没有的元素 class 和长度)!直到稍后某个时候的构造函数调用才传递这些。

有很多方法可以解决这样的问题,但它们都有点恶心。将构造封装在静态工厂方法中更有意义,因为这样你就 不能写 new StructuredArray...,而且 JVM 不必使用任何 "magic" 来计算在 new 指令中为 StructuredArray 分配多少内存,因为不可能有任何这样的指令*。

如果以后的某些 JVM 想要提供分配连续数组的静态工厂的内部实现,那没问题——它在工厂方法调用中获得所需的所有信息。

注意* - 是的,好的,从技术上讲,您 可以 编写 new StructuredArray...,但它对您来说不是一个有用的对象。