Immutable classes in Java
Hi there!
Now we are going to talk about immutable classes in Java and show how to make the class immutable.
The class is called immutable if it is impossible to change its state and content after the initialization (creation).
The benefits of such classes are:
- safety: you, as a developer, can be sure that no one is able to change their state;
- thread-safety: the same as mentioned above is also actual for the multithreaded environment;
- cacheable: instances could be easily cached by VM cache or custom implementation, as we are 100% sure, that their values are not going to be changed;
- hashable: such classes could be safely put inside the hash collections (like
HashMap
,HashSet
etc) - of course, ifequals()
&hashCode()
methods are overridden in a proper way.
So, let’s try to implement the immutable class by taking the mutable one and changing its implementation to the immutable step by step.
Consider, we have the following typical POJO class Hobbit
:
Hobbit
class has a field address
of type Address
:
Address
class is not going to be changed in this article, so, in case of any questions, please reference to the embedded code above.
The first issue to be noticed is setters, that allow changing the value of each class field. So let’s remove those methods:
1. removing setters
Looks better, but now the user of our class has no chances to set the values to class fields. So, let’s provide the appropriate constructor:
2. adding all args constructor
Are we done now? Not yet, there are several issues left. First of all, our immutability could be hacked by using one of the OOP principles — inheritance. Let me demonstrate this with a simple example:
If we run the Hack
class, we’ll get the following output:
Frodo BagginsImmutability has been hacked!Mr. Underhill
To fix this issue we have to:
3. marking class as final to protect it from being extended
Now if we run Hack
class one more time, the output will be correct:
Frodo BagginsImmutability has been hacked!Frodo Baggins
There is one more interesting issue left: as we know, in Java when we pass the value of an object, we are passing the reference to it. So the state of the objects could be changed by the side effects. Let’s get back to the code to see this:
The output of this code is:
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Sword, Ring of Power] Immutability has been hacked! Hobbit country: Isengard
Hobbit city: Saruman tower
Hobbit stuff: []
This issue could be fixed by:
4. initializing all non-primitive mutable fields via constructor by performing a deep copy
Now the output of Hack
class is more predictable:
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Sword, Ring of Power]Immutability has been hacked!Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Sword, Ring of Power]
But the same side effect issue could be produced through getter methods:
Output:
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Sword, Ring of Power] Immutability has been hacked! Hobbit country: Isengard
Hobbit city: Saruman tower
Hobbit stuff: []
So, it goes with another step to be done:
5. performing cloning of the returned non-primitive mutable object in getter methods
Now the Hack
output is:
Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Sword, Ring of Power]Immutability has been hacked!Hobbit country: Hobitton
Hobbit city: Shire
Hobbit stuff: [Sword, Ring of Power]
There is one more optional step to be done, but if all other prerequisites are met, it is not necessary. Anyway, let’s mentioned it:
6. marking all class fields as final (optional)
And that’s it, we are done — our Hobbit
class is immutable now.
Let’s summarize out steps:
- removing setters;
- adding all args constructor;
- marking the class as
final
to protect it from being extended; - initializing all non-primitive mutable fields via constructor by performing a deep copy;
- performing cloning of the returned non-primitive mutable object in getter methods;
- marking all class fields as
final
(optional step).
Of course, there are other ways to make class immutable (for example, using Builder pattern), but it is out of the scope of the current story.
Have fun!