Valhalla (0): 序言

在 JDK 1.8 中,增加了 java.util.Optionaljava.time.LocalDateTime 等类,在这些类的注释中有这么一行:

This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of Optional may have unpredictable results and should be avoided.

引入了一个新的概念,基于值(value-based)的类。

JDK 16 的新特性中,还有一个不太起眼的 JEP 390: Warnings for Value-Based Classes。只是对基于值的类增加了一些警告信息,对功能没有任何影响。

可就在这个不起眼的概念背后,却跟一个足以颠覆 Java 的项目有关,这个项目就是 Valhalla

系列文章(未完待续)

Valhalla

https://openjdk.java.net/projects/valhalla/

在 OpenJDK 中有一些很重要的项目比如 Amber Loom Panama Valhalla 等。相当于是 OpenJDK 的分支,进行一些探索,这些项目带来的改动最终有可能会合并到正式版本的 JDK。

Valhalla 项目在 14 年就启动了,就两个目标:

  • Value Types(值类型)
  • Generic Specialization(特化泛型)

JEPs

目前已经发布的 JEP:

候选状态(Candidate)的 JEP:

候选意味着很有可能在近期的版本加入预览

还有草稿状态的 JEP:

  • JEP draft: Universal Generics (Preview)
  • JEP draft: Value Objects (Preview)

设计文档

背景 Background: How We Got the Generics We Have

现状 The State of Valhalla

  1. The Road to Valhalla
  2. The Language Model
  3. The JVM Model

为什么会颠覆 Java

未来会出现如下颠覆性的代码:

1
2
3
4
5
6
7
8
9
10
11
// 目前 new 的对象一定是不相等的
assert new Point() == new Point();
// 基本类型的泛型
List<int> ints = new ArrayList<>();
// 数组协变,目前的数组是不变的
Object[] array = new int[0];
// 目前 true 不是对象
assert true instanceof Boolean;
true.toString();
// 目前 new 一个实例,getClass 一定是 类名.class
assert new Point().getClass() != Point.class;

同时有如下颠覆性改变:

  • 所有值都是对象(包括 intdouble 等)
  • 不是所有的对象都保存在堆中
  • (可能)Arrays 等只需要一组 sort 方法了(以前 int[] double[] 都有自己的 sort 方法)
  • (可能)java.lang.Object 会变成抽象类,但是可以继续 new Object()

翻译约定

后面有很多内容是翻译的,会按照如下约定:

英语 中文 说明
Bsic Primitive Type 基本类型
Primitive Data Type 基本类型 同基本数据类型/原始类型
Reference Type 引用类型
Value Type 值类型
Basic Primitive Value 基本值
Reference Value 引用值
Identity 指“有唯一标识的”,后续不做翻译
Identity-free 指“没有唯一标识的”,后续不做翻译
Identity-related Behavior Identity 相关操作
Identity-sensitive Operation Identity 相关操作
Primitive Wrapper Class 包装类
Primitive Object 原始对象
Primitive Classe 原始类
Identity Object Identity 类
Identity Classe Identity 对象
Primitive Value Type 原始值类型
Primitive Reference Type 原始引用类型
Reference-favoring Primitive Classe 引用优先的原始类

概念解释

Type

有这么几个跟类型相关的概念

  • 值(Value):在 Java 中有基本值和引用值。
  • 类型(Type):描述值和可执行操作的集合,每一个值对应一个类型。在 Java 中有值类型和引用类型,一些语言还有指针类型。
  • 类(Class):一种具体的类型 class type,或指 class 的声明。
  • 对象(Object):类的实例。
  • 类对象(Class Object):特指 java.lang.Class 的实例,在运行时表示一个类。

为什么说 Class 是一种 Type,来看一下 Class 的定义(Class implements Type):

1
2
3
4
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {}

除了类类型,还有 ParameterizedTypeGenericArrayType 等类型,一般用在反射相关的 API。同时类型还有一个关联的概念类型系统,编译期也会有类型检查。

值和类型对应,类和对象对应,可以参考 java.lang.reflect.Type 的相关文档。总的来说类型是一个更加抽象的概念。

Value Type

跟引用相反,数据直接存储在栈中,更像是一个盒子,包装了若干个值,这个概念可以参考 C 语言中的 struct。

Identity

在面向对象的程序设计中,实例化一个对象之后,不管我们怎么修改它的状态(属性值),我们都说它还是同一个对象(虽然它变化了),因此有两种维度来比较一个对象,状态和 Identity。

Identity 就是唯一标识,在实现上可以被认为是在内存中的地址(不考虑 GC 的话)。

具体到 Java 的话 equals 方法用来比较对象的状态,== 和 != 用来比较对象的 Identity(通常我们说的是不是同一个对象),每个对象都有唯一的 Identity(这个概念可以参考 System.identityHashCode 这个方法),因此

1
2
assert new Object() != new Object();
assert new Integer(1) != new Integer(1);

那么 Identity-free/Identity-less 就是反过来的意思,例如基本类型就是 Identity-free 的,所以

1
assert 1 == 1;

所以在目前版本的 Java 中,所有的引用类型都是 Identity 的、基本类型都是 Identity-free 的,不过我们一般不会这么去描述,但这个概念是非常重要的。

Identity 不太好翻译,后面就当形容词用了,指“有唯一标识的”,Identity-free 指“没有唯一标识的”

在 Java 中有一些对象,其实是不需要区分不同实例的,例如

  • Integer.valueOf(1000) 就表示的是 1000 这个数字,没必要区分这个 1000 和那个 1000。
  • LocalTime.of(10, 0) 就表示“早上 10 点”,区分不同“早上 10 点”的实例是没有意义的。如果有一个方法是 doSomeThing(LocalTime localTime) ,那么几乎所有的场景下,我传入不同实例的“早上 10 点”应该都是一样的行为。

不是说不能区分,而是说没意义,这里只解释 Identity 这个概念。

Value-Based Classes

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/doc-files/ValueBased.html

这个概念最早在 java 8 就提出了
https://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html

在上面的讨论中,我们发现有一些类型应该是 Identity-free 的,例如 java.lang.Integerjava.time.LocalTime,不应该去区分不同的实例。

再具体一点,满足下列规范的类被称为基于值的类(value-based classes)

  • 所有的实例字段都是 final 的(不可变的,但可以包含引用并指向可变对象,例如 java.util.Optional)。
  • equals、hashCode 和 toString 的实现只依赖实例字段的值(包含引用的对象)而不是 Identity。
  • 方法在处理相等的(equals)实例时行为是一致的(认为实例是可自由替换的(freely substitutable))。
  • 实例不执行同步操作(synchronized)。
  • 没有声明可访问的构造函数(可以包含废弃的)。
  • 不提供任何可以生成独立 Identity 的对象创建机制:
    • 简单来说就是任何方式创建的对象都是 Identity-free 的。
    • 特别的,所有的工厂方法都必须保证,独立生成的两个实例如果 equals 那么一定 == 。
    • 特别的,Object.clone() 应该返回相同 == 的对象。
  • final 的,父类(们)只能是 Object 或者没有字段、没有初始化代码、构造函数都是空的抽象类。

如果基于值的两个对象是相等的(equals),程序也不应该尝试去区分它们

  • 不管是通过直接或间接的引用、使用 Identity hash、序列化等任何机制。
  • 不应该执行同步操作(synchronized),因为没有办法保证独占对象监视器。
  • 上述行为都是 Identity 相关的行为(Identity-related Behavior),在未来版本的 Java 中可能会变化,例如同步会失败。

目前基于值的类只是一种定义,为了后续版本更好的发展,在 JDK 16 中引入了新的 JEP 390: Warnings for Value-Based Classes,会对上述的一些行为进行警告。

只增加了警告信息,没有任何功能变化
It does not make any changes to the Java Language or Java Virtual Machine specifications.

Primitive Class and Primitive Objects

基于值的类只是一个概念,任何一个类只要满足上述规范就是基于值的,目前并不会对实际写代码产生任何影响。

Valhalla 项目中引入一种新的类型,原始类(primitive class)(预览)

  • 基于值的
  • Identity-free

因此在未来的 Java 代码中可能会出现

1
2
3
primitive class Point {...}
assert new Point() == new Point();
assert new Point() == Point.default;

是不是感觉 Java 的底层逻辑都改变了,尤其是两个 new 出来的对象居然是 == 的,在基本类型和引用类型之间增加了一种新的类型。

Codes like a class, works like an int.(像 class 一样编写,像 int 一样运行)
这里只解释新的概念,后面会进一步探讨为什么需要增加新的类型

在以前的草案中称为 “inline class” / “inline type” ,来看看目前最新的定义
https://mail.openjdk.java.net/pipermail/valhalla-spec-experts/2020-October/001415.html

对象会细分成两种类型

  • Primitive Object 原始对象:是一种新的 Identity-free 的对象,相等、同步等相关操作的行为会有所不同
  • Identity Object:除了 Primitive Object 之外的对象(包括数组)

类型也会进行细分

  • Identity Class:实例是 Identity Object 的类。
  • Primitive Class 原始类(以前叫 Inline Class):是一种特殊的类,它的实例是原始对象。类是 final 的,并且受到各种限制。非原始类要不然是 Identity Class 要不然是抽象类(或者是 java.lang.Object)。
  • Primitive Value Type 原始值类型(以前叫 Inline Type):是一种类型,是值为原始对象(就是对象本身,而不是引用)。每一个原始类都有一个原始值类型,通常就是 类名 来表示。
  • Primitive Reference Type 原始引用类型:是一种类型,值为原始对象的引用或者 null。每一个原始类都有一个原始引用类型,通常就是 类名.ref 来表示。
  • Primitive Type:Primitive Value Type 或者 Primitive Reference Type。
  • Reference Type:仍然表示对象的引用或者 null(跟以前相同,强调包含了 Primitive Reference Type)。

类 Class / 类型 Type 是两个概念,参考上文。

在 Java 语言中,基本类型将(计划)变成原始对象,而用 java.lang.Integer 等作为它们的原始类。在需要的时候,可以使用内置的原始值类型(built-in primitive value type)指代它们的类型。

意味着,后面就没有基本类型了,只有原始对象和 Identity 对象,Java 将真正变成一切皆对象。
int double 等含义会发生非常大的变化,但在使用上几乎没有变化。

这些概念有一些繁琐,我们来举一些例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java.lang.Integer.val.class  // 原始类
int.class // 原始类
java.lang.Integer.class // 原始类
java.lang.Object.class // 非原始类

int.class 等价于 Integer.val.class

java.lang.Integer.val // 原始值类型
java.lang.Integer // 原始引用类型
int.ref // 原始引用类型
int // 原始值类型(关键字)

int.ref 等价于 Integer
int 等价于 Integer.val
Author

Xinyu Liu

Posted on

2021-10-01

Updated on

2022-05-07


Comments