Are You Considering Making Your Classes Immutable?
No matter if you are a senior or junior Java developer, I am sure that in several situations you have not considered your class should be immutable or you have not even thought about immutability. This is a common mistake, so let me show you why at least you should think about it.
What does making your classes immutable mean?
An immutable class is a class whose instances’ content cannot be modified once they have been created. That is to say, to make your classes immutable you just need to protect it against modifications.
There are some rules you must follow to achieve immutability in your class:
- Protect your fields from direct access declaring them as
private
. - Avoid inheritance declaring the class as
final
or using static factories as a constructor. - Make your fields
final
in order to assign the content only in the declaration. - If your class has fields referring to mutable objects, ensure that the object references cannot be obtained. For this, always make deep copies of the mutable objects.
- Do not provide mutators, which are methods that modify the object’s state.
You can read below a representation of a complex number (a number with real and imaginary parts) as an example of an immutable class complying following the above-mentioned rules:
public final class Complex {
private final double real;
private final double imaginary;
public Complex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
}
public double realPart() {return real;}
public double imaginaryPart() {return imaginary;} public Complex plus(Complex c) {
return new Complex(real + c.real, imaginary + c.imaginary);
}
public Complex minus(Complex c) {
return new Complex(real - c.real, imaginary - c.imaginary);
}
// ...
}
An alternative design could be making all the constructors private, or package-private, and adding static factory methods as a constructor. This design would give more flexibility (allowing the use of several package-private implementation classes), as well as an improvement in the readability by the use of static factories, and avoid adding final to the class.
public class Complex {
private final double real;
private final double imaginary;
private Complex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
} public static Complex valueOf(double real, double imaginary{
return new Complex(real, imaginary);
}//...
But, why should you consider making your classes immutable?
Immutability brings you a certain number of benefits with few downsides. For this reason, the Java platform libraries contain many immutable classes like String
, BigDecimal
, or even the primitive wrapper classes. Here are some of those advantages:
- The main benefit is the immutable objects’ simplicity. They can only have one state in the object lifecycle, the creation state, thus avoiding problems with the state transitions and providing you a clearer flow view without any extra effort.
- Immutable objects are inherently thread-safe and they do not need synchronization. Since the object has only got one state, it is not possible to corrupt the object or to have undesired behaviours caused by multiple threads.
- Immutable objects provide failure atomicity for free. Due to the impossibility to modify the state, you cannot have temporary inconsistency.
- Maintainability improvement by composing a class (whether mutable or immutable) or a component with immutable objects. It is easier to maintain and use classes with fields you know are invariants. One of the best examples is using those fields in maps because of the impossibility of changing their value, so you do not need to worry about value changes once they are inserted in a map or set.
However, there are also some drawbacks. The major disadvantage is the need for the object’s creation for each distinct value, and the cost could be high in large objects. There are several approaches to reduce this downside. A very used one is creating a mutable companion class.
The most known example of a mutable companion class is the String
class (immutable), whose mutable companion is StringBuilder
. If the cost is high for the functionality you need, you can consider using companions. Consider the following example to see how that works in terms of what we store in Java memory.
String s = "Hello";
s += "World";
//Memory -> "Hello" , "HelloWorld"StringBuilder s = new StringBuilder ("Hello");
s.append("World");
//Memory -> "HelloWorld"
Conclusion
We have analyzed the multiple benefits of immutability and also how to deal with its disadvantages. Besides, we have seen why it is important to think about your classes’ design and to make your code as simple as possible to improve maintainability while reducing possible errors and technical debts.
To conclude, remember that classes should be immutable unless you have a good reason to make them mutable. If you cannot make them immutable, reduce the mutability as much as possible.
You can go deep into this topic by reading the book Effective Java by Joshua Bloch, which I have taken as my main reference.