An immutable class is a class whose instances cannot be modified after it is created. The values of the instance variables is fixed for the entire lifetime of the object. There are a lot of benefits in making a class immutable. In this post, we will look at them.
Making a class immutable
Have a look at my previous post on how to make a class immutable. It lists the steps/rules a class must follow for it to be immutable. That post starts with a mutable class and applies those steps and ends up with a class that is immutable.
Now, we will start with the benefits of immutable class in java.
Benefits of immutable class
The first and the foremost benefit is that immutable classes are simple. An immutable class can just be in one externally visible state (As of now just think of this as ‘one state’. I will come to the externally visible part later).
On the contrary, a mutable object can have a lot of states. A mutable object too starts with one state (initial values of the instance variables). Each mutation of the instance variable takes the object to another state. This brings in the need to document all possible states a mutable object can be in. It is also difficult to avoid inconsistent or invalid states. Hence, all these makes a mutable object difficult to work with.
2. Thread safety
Immutable objects are inherently thread-safe. They do not require synchronization. Since there is no way the state of an immutable object can change, there is no possibility of one thread observing the effect of another thread. We do not have to deal with the intricacies of sharing an object among threads (like locking, synchronization, making variables volatile etc.). Thus, we can freely share immutable objects. This is the easiest way to achieve thread safety.
3. Enables reuse
Immutable objects encourage to cache or store the frequently used instances rather than creating one each time. This is because two immutable instances with the same properties/values are equal.
Some examples of this being applied are as follows
Primitive wrapper classes
Creating primitive wrapper objects (Integer, Float, Double etc) using static factory method valueOf does not always return new wrapper instances. In case of Integer, they cache Integer values from -128 to 127 by default.
Integer oneV1 = new Integer(1); Integer oneV2 = new Integer(1); System.out.println(oneV1 == oneV2); //false System.out.println(oneV1.equals(oneV2)); //true oneV1 = Integer.valueOf(1); //returns cached instance oneV2 = Integer.valueOf(1); //returns cached instance System.out.println(oneV1 == oneV2); //true System.out.println(oneV1.equals(oneV2)); //true
BigInteger stores some common BigInteger values as instance variables.
/** * The BigInteger constant zero. * * @since 1.2 */ public static final BigInteger ZERO = new BigInteger(new int, 0);
This reduces the memory footprint and the garbage collection costs.
Tip: When designing an immutable class try to use a static factory method and a private constructor rather than providing a public constructor. This will enable us to add caching of some immutable instances later.
4. Share the Internals of immutable class
We can share the internal data structures among immutable objects.
Example: The BigInteger class has an int for representing the sign and an int array for the magnitude. When we negate the object, it returns a new BigInteger with the sign negated but same magnitude. Here, it doesn’t copy the magnitude array. Instead, the new BigInteger instance and the old one points to the same array. This is fine as the underlying array instance (internals) are not exposed and hence it cannot be mutated.
5. Building blocks for other objects
The immutable objects make a great building block for building other objects (mutable or immutable). An example is immutable objects make a great fit to be used as keys of a map and in sets.
In the rules to make a class immutable, there was a rule stating all the fields must be final. But this is not always necessary. What it actually meant is all externally visible fields must be final. We can thus have an internal field as non-final.
Say, for example, you might want to cache the result of some computation in a non-final field the first time they are done. Any subsequent computation can just return the cached result. This can only work if the object is immutable. If the object was mutable, its state would change thus making the computed result obsolete (when the computation is based on the instance variable’s values).
- Strive to make objects immutable. Add mutability only if you are unable to make it immutable.
- Immutable objects are simple and provide thread-safety for free.
- The Immutable instances can be cached thus reducing the need to create the same object each time.
- An Immutable object makes a good candidate for map keys and sets.
- Immutable classes can have some mutable fields for caching some results as long as this field is not exposed to the clients.