How to implement values with immutable classes

March 05, 2018

Classes representing values should have the same properties than primitive types. Primitive types represent basic values like int or char in Java. Primitive types have no identity and they are immutable.

In the following, we will see that those two properties are also useful for classes representing properties.

No identity

Two values are equal if they have the same external visible state: For example, if you have two variables a and b with the value 5, they are equal:

 int a = 5;
 int b = 5;
 a == b // true

Immutable

Values are immutable. If you a modify a value it becomes a new value. In the following example, we modify the variable a which leads to a new value b. A and b are nor equal.

 int a = 5
 int b  = a + 2;
 a == b // false

A value class

Now let us look at a class representing a value with those two properties, the java.time.Instant class. This class represents an instant in time. Let us first look at the field declaration of this class:

package java.time;

public final class Instant
{
    private final long seconds;
    private final int nanos;    
    // static fields and methods omitted 
}   

Declaring the field final makes this class immutable. Declaring a field as final as the two fields in the example, line 5 and 6, lets the compiler check that the fields are not modified after the constructor of the class was called. Note that final is a field modifier. It makes the field itself immutable not the object the field references to. So the type of the final field must also be immutable or a primitive type like in the example.

Next, let us look at the equal method to see how equality is implemented for this class:

public boolean equals(Object otherInstant) {
        if (this == otherInstant) {
            return true;
        }
        if (otherInstant instanceof Instant) {
            Instant other = (Instant) otherInstant;
            return this.seconds == other.seconds &&
                   this.nanos == other.nanos;
        }
        return false;
    } 

As we see two Instant objects are equal if their external visible state is equal.

Immutability but an identity

Now let us look at a class with is immutable but uses its identity for equality, the Object class. The Object class is useful when you only need an identity but no state. Like in the following example, from the JDK 9 java.util.concurrent.CopyOnWriteArrayList, where we need the identity as a monitor for synchronization:

public class CopyOnWriteArrayList<E>
{
  final transient Object lock = new Object()
   public boolean add(E e) {
        synchronized (lock) {
            // other statements omitted
        }
    }
}

To represent a value a class with identity is not useful.

No identity but mutable

The following shows a mutable value class, the java.util.Date class:

public class Date
{
     private transient BaseCalendar.Date cdate;
	 private transient long fastTime;
	 public void setTime(long time) {
        fastTime = time;
        cdate = null;
    }
    public boolean equals(Object obj) {
        return obj instanceof Date && getTime() == ((Date) obj).getTime();
    } 
	public long getTime() {
        return getTimeImpl();
    }
    private final long getTimeImpl() {
        if (cdate != null && !cdate.isNormalized()) {
            normalize();
        }
        return fastTime;
    } 
    // static fields and other methods omitted 
}

The two fields cdate and fastTime are not final and can be changed by the setTime method making the class mutable. The equals method checks the externally visible state for equality. While it is possible to implement values with mutable classes, immutable classes are easier to use.

Advantages of immutable value classes

Immutable classes cannot change their state. This property is useful in specific scenarios in single-threaded programs, for example when you use them as keys in hash maps. And it makes writing multi-threaded programs much easier.

Immutable classes do not change their value in the middle of an operation without using synchronized blocks. By avoiding synchronization blocks you avoid deadlocks. And since you are always working with an unchangeable consistent state you avoid race conditions.

Usage of identity

While it is still possible to access the identity of a value object it is probably an error. For example, the use of == instead of equals is probably incorrect:

Integer a = new Integer(5);
Integer b = new Integer(5);
a.equals(b) // true
a == b      // false

You probably expect true when you compare two Integer of value 5, so the use of == is incorrect. The following methods are using the identity of the object and should be avoided when using value classes:

  • synchronized statement
  • System.identityHashCode
  • Object.notify and wait

The future: JEP 169, Value Objects

Implementing values using classes requires more memory than their primitive counterparts. A solution to this problem is implemented by The Java enhancement proposal 169, Value Objects,. It will allow you to create value classes with similar memory characteristics as primitive types.
The idea is to implement a new operator lockPermanently which converts an object into a new state with memory consumption similar to a primitive type. Using an operation which requires the identity of the value object like == or synchronized on such a locked object will be forbidden.

Conclusion and what is next?

Primitive types represent basic values. Primitive types are immutable and have no identity. We have seen how to implement classes with the same properties. The usage of the identity of a value class, while still possible, is probably an error. Making value classes immutable makes them easier to use, especially for thread safe software.

In the next blog post, we will look at one type of value classes, messages, to write thread safe software. I would be glad to hear from you if you use value classes in your application.

testing multi-threaded applications on the JVM made easy

LEARN MORE

© 2020 vmlens Legal Notice Privacy Policy