Value-Based Classes in Java – Java 中基于值的类

最后修改: 2023年 9月 23日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

In this tutorial, we’ll talk about a very interesting feature that Project Valhalla brings to the Java ecosystem, Value-based Classes. Value-based classes were introduced in Java 8 and have gone through major refactors and enhancements in later releases.

在本教程中,我们将讨论 Valhalla 项目为 Java 生态系统带来的一个非常有趣的功能–基于值的类。基于值的类是在 Java 8 中引入的,在后来的版本中经历了重大的重构和增强。

2. Value-based Classes

2.基于价值的班级

2.1. Project Valhalla

2.1 瓦尔哈拉项目

Project Valhalla is an experimental project by OpenJDK to add new features and capabilities to Java. The primary goal of this initiative is to add improved support for value types, generic specialization, and performance improvements while maintaining complete backward compatibility.

Project Valhalla 是 OpenJDK 的一个实验项目,旨在为 Java 添加新特性和功能。该计划的主要目标是在保持完全向后兼容性的同时,增加对值类型、泛型专用化和性能改进的支持。

Value-based classes are one of the features introduced by Project Valhalla to introduce primitive, immutable values to the Java language without the added overhead that traditional object-oriented classes bring.

基于值的类是 Valhalla 项目引入的功能之一,目的是在 Java 语言中引入原始、不可变的值,而不会像传统的面向对象类那样增加开销。

2.2. Primitives and Value-Types

2.2.基元和值类型

Before we come to the formal definition of value-based classes, let’s look at two important semantics in Java – primitives and value types.

在正式定义基于值的类之前,我们先来看看 Java 中的两个重要语义–基元和值类型。

Primitive data types, or primitives, in Java, are simple data types that represent a single value and are not objects. Java provides eight such primitive data types: byte, short, int, long, float, double, char, and boolean. While these are simple types, Java provides wrapper classes for each of these for us to interact with them in an object-oriented way.

Java中的基元数据类型(或称基元)是表示单一值的简单数据类型,而不是对象。Java 提供了八种这样的基元数据类型:byte, short, int, long, float, double, char,boolean.虽然这些都是简单的类型,但 Java 为每个类型提供了封装类,以便我们以面向对象的方式与它们交互。

It is also important to remember that Java performs auto-boxing and unboxing automatically to convert between the object and primitive type efficiently:

同样重要的是要记住,Java 会自动执行自动装箱和拆箱操作,以便在对象类型和基元类型之间进行有效转换:

List<Integer> list = new ArrayList<>();
list.add(1); // this is autoboxed

Primitive types live on the stack memory, while the objects that we use in our code live on the heap memory.

原始类型位于堆栈内存中,而我们在代码中使用的对象位于堆内存中。

Project Valhalla introduced a new type in the Java ecosystem that is somewhat between an object and a primitive, and it is termed a value-type. Value types are immutable types, and they do not have any identity. These value types also do not support inheritance.

Valhalla 项目在 Java 生态系统中引入了一种介于对象和基元之间的新类型,它被称为值类型。这些值类型也不支持继承。

Value types are not addressed by their reference but by their values, just like primitives.

与基元一样,值类型也不是通过引用来寻址,而是通过值来寻址。

2.3. Value-Based Classes

2.3.基于价值的类别

Value-based classes are classes that are designed to behave like and encapsulate value-types in Java. The JVM can freely switch between value types and its value-based class, much like auto-boxing and unboxing. Value-based classes are hence, identity free, for the same reason.

基于值的类是设计用来像 Java 中的 值类型一样运行并封装 值类型的类。JVM 可以在值类型和基于值的类之间自由切换,就像自动装箱和取消装箱一样。因此,基于值的类是无标识的,原因也是如此。

3. Properties of Value-Based Classes

3.基于价值的类别的特性

Value-based classes are classes that represent simple immutable values. A Value-based class has several properties that can be categorized into some general themes.

基于值的类是表示简单的不可变值的类。基于值的类具有若干属性,这些属性可归类为一些一般主题。

3.1. Immutability

3.1.不变性

Value-based classes are meant to represent immutable data, similar to primitives like int, and have the following characteristics:

基于值的类旨在表示不可变的数据,类似于 int, 等基元,并具有以下特点:

  • A value-based class is always final
  • It contains only the final fields
  • The class can extend the Object class or a hierarchy of abstract classes that declare no instance fields

3.2. Object Creation

3.2.创建对象

Let’s understand how creating new objects of value-based classes works:

让我们来了解一下如何创建基于值的类的新对象:

  • The class does not declare any accessible constructor
  • In case there are accessible constructors, they should be marked as deprecated for removal
  • The class should be instantiated only through factory methods. The instance received from the factory may or may not be a new instance, and the calling code should not make any assumption about its identity

3.3. Identity and equals(), hashCode(), toString() Methods

3.3.同一性和 equals(), hashCode(), toString() 方法

Value-based classes are identity-free. As they are still classes in Java, we need to understand how methods inherited from Object class happens:

基于值的类是无标识的。由于它们仍然是 Java 中的类,我们需要了解从 Object 类继承的方法是如何发生的:

  • The implementations of equals(), hashCode(), and toString() are defined solely based on the values of its instance members and not from their identities, nor any other instance’s state
  • We consider two objects to be equal solely on the objects’ equals() check and not on reference-based equality, i.e. ==
  • We can use two equal objects interchangeably, and they should produce the same result on any computation or method invocation.

3.4. Some Additional Caveats

3.4.一些额外的注意事项

We should consider some additional limitations while working with value-based classes:

在使用基于价值的课程时,我们还应该考虑一些其他限制因素:

  • Two objects, which are equal based on the equals() method, might be different objects in the JVM or the same
  • We cannot ensure exclusive ownership of the monitor, making instances unsuitable for synchronization

4. Examples of Value-Based Classes

4.基于价值的类别示例

4.1. Value-Based Classes in the JDK

4.1.JDK 中基于值的类

There are several classes in the JDK that follow the Value-based class specification.

JDK 中有几个类遵循基于值的类规范。

When it was first introduced, java.util.Optional and the DateTime API (java.time.LocalDateTime) were value-based classes. As of Java 16 and beyond, Java has defined all the wrapper classes of primitive types such as Integer and Long as value-based.

最初引入时,java.util.OptionalDateTime API(java.time.LocalDateTime都是基于值的类。从 Java 16 及以后,Java 已将 IntegerLong 等基元类型的所有封装类定义为基于值的类。

These classes have the @ValueBased annotation from the jdk.internal package present:

这些类具有来自 jdk.internal 包的 @ValueBased 注解:

@jdk.internal.ValueBased
public final class Integer extends Number implements Comparable<Integer>, Constable, ConstantDesc {
    // Integer class in the JDK
}

4.2. Custom Value-Based Class

4.2.自定义基于价值的类别

Let’s create our custom class which follows the value-based class specification defined above. For our example, let’s take a Point class which identifies a point in 3D space. The class has 3 integer fields x, y, and z.

让我们按照上面定义的基于值的类规范创建自定义类。在我们的示例中,让我们创建一个Point类来标识三维空间中的一个点。该类有 3 个整数字段 xyz

We can argue that the Point definition serves as a good candidate for a value-based class because a specific point in space is unique and can be referred to only by its value. It is constant and unambiguous, much like an integer of value 302.

我们可以说,定义是基于值的类的理想候选,因为空间中的特定点是唯一的,只能用其值来表示。它是恒定和明确的,就像值为 302 的整数一样。

We’ll start by defining the class to be final and its attributes x, y, and z as final. Let’s also make the constructor private:

首先,我们要将类定义为 final,并将其属性 xyz 定义为 final。我们还要将构造函数设置为私有:

public final class Point {
    private final int x;
    private final int y;
    private final int z;
    // inaccessible constructor
    private Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    // ...
}

Now, let’s have the origin(0, 0, 0) instance of the class created beforehand, and we return the same instance every time there is a call to create a point with x = 0, y = 0, and z = 0:

现在,让我们事先创建类的 origin(0, 0, 0) 实例,每次调用创建一个 x = 0y = 0,z = 0 的点时,我们都返回相同的实例:</em

private static Point ORIGIN = new Point(0, 0, 0);

We now need to provide an object creation mechanism in the form of a factory method:

现在,我们需要以工厂方法的形式提供一种对象创建机制:

public static Point valueOfPoint(int x, int y, int z) {
    // returns a cached instance if it is the origin, or a new instance
    if (isOrigin(x, y, z)) {
        return ORIGIN;
    }
    return new Point(x, y, z);
}

// checking if a point is the origin
private static boolean isOrigin(int x, int y, int z) {
    return x == 0 && y == 0 && z == 0;
}

The factory method valueOfPoint() could return a new instance or a cached one depending on the parameters. This forces the calling code not to make any assumption on the state of the object or compare references of two instances. 

工厂方法 valueOfPoint() 可以根据参数返回一个新实例或一个缓存实例。这迫使调用代码不能对对象的状态做出任何假设,也不能比较两个实例的引用。

Finally, we should define the equals() method based only on the values of instance fields:

最后,我们应仅根据实例字段的值定义 equals() 方法:

@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);
}

We now have a class Point, which can behave as a value-based class. We can put the @ValueBased annotation to the class after importing it from jdk.internal package. However, it is not mandatory for our case.

现在我们有了一个类 Point,它可以作为一个基于值的类。我们可以从 jdk.internal 包中导入 @ValueBased 注解。但是,在我们的情况下,这并不是强制性的。

Let’s now test that two instances of the same point in space denoted by (1,2,3) are equal:

现在,让我们来检验空间中以(1,2,3)表示的相同点的两个实例是否相等:

@Test
public void givenValueBasedPoint_whenCompared_thenReturnEquals() {
    Point p1 = Point.valueOfPoint(1,2,3);
    Point p2 = Point.valueOfPoint(1,2,3);

    Assert.assertEquals(p1, p2);
}

Additionally, for the sake of this exercise, let’s also see that two instances, if compared by reference, are the same when two origin points are created:

此外,在本练习中,我们还可以看到,如果通过引用进行比较,当创建两个 起源点时,两个实例是相同的:

@Test
public void givenValueBasedPoint_whenOrigin_thenReturnCachedInstance() {
    Point p1 = Point.valueOfPoint(0, 0, 0);
    Point p2 = Point.valueOfPoint(0, 0, 0);

    // the following should not be assumed for value-based classes
    Assert.assertTrue(p1 == p2);
}

5. Advantages of Value-Based Classes

5.按价值分级的优势

Now that we know what value-based classes are and how we can define one, let’s understand why we might need value-based classes at all.

既然我们已经知道了什么是基于值的类以及如何定义基于值的类,那么让我们来了解一下为什么我们需要基于值的类。

Value-based classes being part of the Valhalla specification, are still in the experimental phase and continue to evolve. Therefore, the benefits of such classes may change over time.

基于价值的类别是 Valhalla 规范的一部分,目前仍处于试验阶段,并将继续发展。因此,这些类别的优势可能会随着时间的推移而改变。

As of now, the most important benefit that comes out of using value-based classes is memory utilization. Value-based classes are more memory efficient as they do not have reference-based identity. Additionally, the JVM can reuse existing instances or create new ones based on the requirements, thereby reducing the memory footprint.

目前,使用基于值的类的最大好处是内存利用率。此外,JVM 可以根据需求重用现有实例或创建新实例,从而减少内存占用。

Also, they do not require synchronization, increasing overall performance, especially in multithreaded applications.

此外,它们不需要同步,从而提高了整体性能,尤其是在多线程应用程序中。

6. Difference Between Value-Based Classes and Other Types

6.基于价值的类别与其他类别的区别

6.1. Immutable Classes

6.1.不可变类

Immutable classes in Java share a lot of common ground with Value-based classes. Hence, it is very crucial to understand the differences between them.

Java 中的不可变类与基于值的类有很多共同点。因此,了解它们之间的区别非常重要。

While value-based classes are new and part of an ongoing experimental feature, Immutable classes have been a core and integral part of the Java ecosystem for a long time. The String class, Enums, and wrapper classes in Java, such as the Integer class, are examples of immutable classes.

基于值的类是一种新特性,也是正在进行的实验特性的一部分,而不可变类则早已成为 Java 生态系统中不可或缺的核心部分。Java中的String类、Enums和封装类(如Integer类)都是不可变类的示例。

Immutable classes are not identity-free like value-based classes. Instances of Immutable classes having the same state are distinct, and we can compare them based on reference equality. Instances of value-based classes do not have the notion of reference-based equality:

不可变类不像基于值的类那样是无标识的。具有相同状态的不可变类的实例是不同的,我们可以根据引用相等来比较它们。基于值的类的实例没有基于引用的平等概念:

Immutable classes are free to provide accessible constructors and can have multiple attributes and complex behaviors. However, value-based classes represent simple values and do not define complex behavior with dependent attributes.

不可变类可自由提供可访问的构造函数,并可拥有多个属性和复杂的行为。但是,基于值的类表示简单的值,不能定义具有依赖属性的复杂行为。

Finally, we should note that value-based classes are, by definition, immutable but not vice-versa.

最后,我们应该注意,根据定义,基于值的类是不可变的,但反之亦然。

6.2. Records

6.2.记录

Java introduced the notion of Records in Java 14 as an easy way to pass around immutable data objects. Records and value-based classes fulfill different purposes even if they seem similar in behavior and semantics.

Java 在 Java 14 中引入了 Records 概念,作为传递不可变数据对象的一种简单方法。尽管记录和基于值的类在行为和语义上看似相似,但它们却实现了不同的目的。

The most noticeable distinction between records and value-based classes is that records have public constructors, while value-based classes lack them.

记录类和基于值的类之间最明显的区别是,记录类有公共构造函数,而基于值的类没有。

7. Conclusion

7.结论

In this article, we talked about value-based classes and the notion of value types in Java. We touched upon the important properties that value-based classes must abide by and the benefits they bring. We also discussed the differences between value-based classes and similar Java concepts, such as immutable classes and records.

在本文中,我们讨论了 Java 中基于值的类和值类型的概念。我们谈到了基于值的类必须遵守的重要属性及其带来的好处。我们还讨论了基于值的类与不可变类和记录等类似 Java 概念之间的区别。

As always, the code snippets used in this article are available over on GitHub.

与往常一样,本文中使用的代码片段可在 GitHub 上获取。