Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

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.

2. Value-based Classes

2.1. Project Valhalla

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.

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.

2.2. Primitives and Value-Types

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

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.

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

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.

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

2.3. Value-Based Classes

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.

3. Properties of Value-Based Classes

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

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

  • 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

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

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

  • 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

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.1. Value-Based Classes in the JDK

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

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.

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

@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

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.

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.

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:

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:

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. 

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

@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.

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

@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

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.

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.

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

6. Difference Between Value-Based Classes and Other Types

6.1. Immutable Classes

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.

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.

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

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.

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

7. Conclusion

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.

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

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.