JEP 草案:不能为null的值类型


Java 目前有类class 和记录record 引用类型。他们仍在计划添加值记录value record和值类型value class。

  • 增加了!,Long!是 Long 的非空版本。
  • 增加了隐式构造函数选项,它告诉 Java,类/记录允许默认值为全零。

C# 引入值类型已有 20 多年了

动机
值对象是一种缺乏标识的特殊对象,因此 JVM 可以在运行时自由复制和重新编码。

堆扁平化是一种对这些对象特别有用的优化方法,在这种方法中,对值对象的引用被编码为该对象字段值的紧凑位向量,而不需要指向不同内存位置的指针。位向量可以直接存储在字段或数值类类型的数组中。与使用堆分配对象和指针的标准编码相比,这种编码策略通常会带来更小的内存占用空间和更好的定位性。

不过,与原始类型相比,值类类型的堆扁平化可能效率不高,因为它必须考虑空null引用。这些引用通常是通过为 "空标志 "保留一些位来编码的,然后这些位就不能用来编码对象的字段值了。因此,举例来说,一个封装 Integer 需要 32 位来编码 int 值,至少还需要 1 位用于空标志,这很可能导致 64 位编码。

此外,值类类型的堆扁平化还受到对象和引用完整性要求的限制:扁平化数据必须足够小,以便原子读写,否则编码数据可能会损坏。在普通平台上,"足够小 "可能意味着只有 32 或 64 位。因此,虽然许多小的值类可以扁平化,但声明 2 个或更多字段的大多数值类都必须编码为普通堆对象(除非字段存储布尔、字符、字节或 short 类型的基元)。即使是一个封装的 Double 也至少需要 65 位(其中一位用于空标志),这超出了许多系统的原子读/写能力。

原始类型则没有这些限制:基元类型的字段在创建时被隐式初始化为零值(或等效值),而不是空值;而且 long 或 double 类型的大型原始变量允许进行非原子更新(参见 JLS 17.7)。因此,举例来说,int 类型的大型数组占用的内存只有 Integer 类型的扁平化数组的一半。

如果 Java 语言有一种类型可以表示对值类实例的引用,但不表示 null,那么就不需要 null 标志,扁平化存储空间的占用空间也不会超过该类字段的占用空间。这种存储空间需要初始化为某个值,

因此打算支持此功能的类需要允许一个默认值,类似于用于初始化内建存储空间的 0 值。有些值类可能还愿意容忍由非原子读写创建的损坏数据。由于不需要跟踪空标志,JVM 对数据完整性的要求可以放宽,允许选择加入的类模仿 long 和 double 的指定行为。这种选择将把管理并发性和处理因竞赛产生的错误的责任转移给用户。


概括:
让存储值对象的变量类型排除 null,从而使存储更紧凑,并在运行时进行其他优化

目标:

  • 为值类引入一种新类型,该类型的值集中不包含 null,就像不能为 null 的原始类型一样。
  • 让值类 "选择加入 "自动创建适当的默认值,用于初始化不存储 null 的字段和数组。
  • 较大的值类可以进一步 "选择加入 "不存储 null 的字段和数组中的非原子编码。
  • 支持现有类的兼容迁移。将这些属性应用于 Java 平台中的值类,包括用于基元盒的类。