Valhalla (0): 序言
在 JDK 1.8 中,增加了 java.util.Optional
、java.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 218: Generics over Primitive Types
- JEP 401 Primitive Objects (Preview)
- JEP 402 Unify the Basic Primitives with Objects (Preview)
候选意味着很有可能在近期的版本加入预览
还有草稿状态的 JEP:
- JEP draft: Universal Generics (Preview)
- JEP draft: Value Objects (Preview)
设计文档
背景 Background: How We Got the Generics We Have
现状 The State of Valhalla
为什么会颠覆 Java
未来会出现如下颠覆性的代码:
1 | // 目前 new 的对象一定是不相等的 |
同时有如下颠覆性改变:
- 所有值都是对象(包括
int
、double
等) - 不是所有的对象都保存在堆中
- (可能)
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 | public final class Class<T> implements java.io.Serializable, |
除了类类型,还有 ParameterizedType
、GenericArrayType
等类型,一般用在反射相关的 API。同时类型还有一个关联的概念类型系统,编译期也会有类型检查。
值和类型对应,类和对象对应,可以参考 java.lang.reflect.Type
的相关文档。总的来说类型是一个更加抽象的概念。
Value Type
跟引用相反,数据直接存储在栈中,更像是一个盒子,包装了若干个值,这个概念可以参考 C 语言中的 struct。
Identity
在面向对象的程序设计中,实例化一个对象之后,不管我们怎么修改它的状态(属性值),我们都说它还是同一个对象(虽然它变化了),因此有两种维度来比较一个对象,状态和 Identity。
Identity 就是唯一标识,在实现上可以被认为是在内存中的地址(不考虑 GC 的话)。
具体到 Java 的话 equals 方法用来比较对象的状态,== 和 != 用来比较对象的 Identity(通常我们说的是不是同一个对象),每个对象都有唯一的 Identity(这个概念可以参考 System.identityHashCode
这个方法),因此
1 | assert new Object() != new Object(); |
那么 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.Integer
和 java.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 | primitive class Point {...} |
是不是感觉 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 | java.lang.Integer.val.class // 原始类 |
Valhalla (0): 序言