1. 引言
本文将探讨 Project Valhalla 为 Java 生态带来的一项重要特性——值类(Value-Based Classes)。值类自 Java 8 引入,在后续版本中经历了重大重构和增强。对于追求高性能开发的你来说,这个特性值得深入了解。
2. 值类(Value-Based Classes)
2.1. Project Valhalla 简介
Project Valhalla 是 OpenJDK 的实验性项目,旨在为 Java 引入新特性。其核心目标是在保持完全向后兼容的前提下,增强值类型支持、泛型特化及性能优化。
值类是 Project Valhalla 引入的关键特性之一,用于在 Java 中表示原始不可变值,同时避免传统面向对象类带来的额外开销。
2.2. 原始类型与值类型
在正式定义值类前,先回顾 Java 中的两个重要概念:原始类型和值类型。
原始类型 是表示单一值的简单数据类型,非对象。Java 提供八种原始类型:byte
、short
、int
、long
、float
、double
、char
和 boolean
。为支持面向对象操作,Java 为每种原始类型提供了对应的包装类。
需注意 Java 会自动执行装箱/拆箱操作,高效转换对象与原始类型:
List<Integer> list = new ArrayList<>();
list.add(1); // 自动装箱
原始类型存储在栈内存,而对象存储在堆内存。
Project Valhalla 引入了一种介于对象和原始类型之间的新类型——值类型。值类型不可变且无身份标识,不支持继承。与原始类型类似,值类型通过值而非引用访问。
2.3. 值类定义
值类是设计用于封装 Java 中值类型的类。JVM 可自由切换值类型与其对应的值类,类似自动装箱/拆箱机制。因此值类也是无身份标识的。
3. 值类的核心特性
值类表示简单不可变值,其特性可归纳为以下几类:
3.1. 不可变性
值类需表示不可变数据,类似 int
等原始类型,具有以下特征:
- ✅ 类必须声明为
final
- ✅ 所有字段必须为
final
- ✅ 只能继承
Object
或无实例字段的抽象类
3.2. 对象创建机制
值类的对象创建需遵循以下规则:
- ❌ 不声明可访问构造器
- ⚠️ 若存在构造器,必须标记为
@Deprecated(forRemoval=true)
- ✅ 仅通过工厂方法实例化,调用方不应假设返回实例的身份(可能是新实例或缓存实例)
3.3. 身份标识与核心方法
值类无身份标识,作为 Java 类需明确 Object
继承方法的行为:
- ✅
equals()
、hashCode()
和toString()
实现完全基于实例字段值 - ✅ 对象相等性仅通过
equals()
判断,禁止使用==
- ✅ 两个相等对象可互换使用,计算结果应完全一致
3.4. 重要注意事项
使用值类时需注意以下限制:
4. 值类示例
4.1. JDK 中的值类
JDK 中多个类遵循值类规范:
- Java 8 引入的
java.util.Optional
和日期时间 API(如java.time.LocalDateTime
) - Java 16+ 将原始类型的包装类(如
Integer
、Long
)正式定义为值类
这些类带有 jdk.internal.ValueBased
注解:
@jdk.internal.ValueBased
public final class Integer extends Number implements Comparable<Integer>, Constable, ConstantDesc {
// JDK 中的 Integer 类实现
}
4.2. 自定义值类实战
以三维空间点 Point
为例,创建自定义值类。空间中的特定点具有唯一性,适合用值表示(类似整数 302)。
首先定义 final
类和 final
字段,构造器设为私有:
public final class Point {
private final int x;
private final int y;
private final int z;
// 私有构造器
private Point(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
// ...
}
预创建原点实例 (0,0,0)
并缓存:
private static Point ORIGIN = new Point(0, 0, 0);
提供工厂方法(根据参数返回新实例或缓存实例):
public static Point valueOfPoint(int x, int y, int z) {
// 返回缓存的原点实例或新实例
if (isOrigin(x, y, z)) {
return ORIGIN;
}
return new Point(x, y, z);
}
// 判断是否为原点
private static boolean isOrigin(int x, int y, int z) {
return x == 0 && y == 0 && z == 0;
}
工厂方法强制调用方放弃对对象身份的假设,禁止引用比较。
最后基于字段值实现 equals()
和 hashCode()
:
@Override
public boolean equals(Object other) {
if (other == null || getClass() != other.getClass()) {
return false;
}
Point point = (Point) other;
return x == point.x && y == point.y && z == point.z;
}
@Override
public int hashCode() {
return Objects.hash(x, y, z);
}
测试相同坐标点的相等性:
@Test
public void givenValueBasedPoint_whenCompared_thenReturnEquals() {
Point p1 = Point.valueOfPoint(1,2,3);
Point p2 = Point.valueOfPoint(1,2,3);
Assert.assertEquals(p1, p2);
}
验证原点实例的缓存行为(⚠️ 实际开发中不应依赖此特性):
@Test
public void givenValueBasedPoint_whenOrigin_thenReturnCachedInstance() {
Point p1 = Point.valueOfPoint(0, 0, 0);
Point p2 = Point.valueOfPoint(0, 0, 0);
// 值类不应假设引用相等
Assert.assertTrue(p1 == p2);
}
5. 值类的优势
值类作为 Valhalla 规范的一部分仍在演进,当前主要有两大优势:
内存效率提升
✅ 无引用身份标识开销
✅ JVM 可灵活复用实例,减少内存占用性能优化
✅ 无需同步机制,提升多线程应用性能
6. 值类与其他类型的区别
6.1. 与不可变类对比
不可变类(如 String
、Enum
、旧版包装类)与值类有本质区别:
特性 | 不可变类 | 值类 |
---|---|---|
身份标识 | ✅ 有(可引用比较) | ❌ 无(禁止引用比较) |
构造器访问 | ✅ 可公开 | ❌ 必须私有/工厂方法 |
复杂行为 | ✅ 支持复杂逻辑 | ❌ 仅表示简单值 |
互操作性 | ❌ 相同状态对象为不同实例 | ✅ 相等对象可互换 |
核心结论:值类本质上是不可变的,但不可变类不一定是值类。
6.2. 与 Record 对比
Java 14 引入的 Record 与值类看似相似,实则不同:
- 构造器差异:Record 必须提供公共构造器,值类禁止
- 设计目标:Record 用于简化数据载体,值类专注于值语义
- 身份约束: Record 可保留身份标识,值类必须无标识
7. 总结
本文深入探讨了 Java 值类的概念、实现规范及其与不可变类、Record 的区别。作为 Project Valhalla 的核心特性,值类通过消除身份标识开销,为内存敏感型和高并发场景提供了新的优化方向。虽然仍在实验阶段,但其设计理念值得在性能关键场景中尝试应用。
本文代码示例可在 GitHub 获取。