In object-oriented programming, an object is immutable if its state can't be modified after it is created.
However, there are not so many immutable classes in JDK. Take, for example, class
Date. It is possible to modify its state using
I don't know why the JDK designers decided to make these two very similar classes differently. However, I believe that the design of a mutable
Date has many flaws, while the immutable
String is much more in the spirit of the object-oriented paradigm.
Moreover, I think that all classes should be immutable in a perfect object-oriented world. Unfortunately, sometimes, it is technically not possible due to limitations in JVM. Nevertheless, we should always aim for the best.
This is an incomplete list of arguments in favor of immutability:
- immutable objects are simpler to construct, test, and use
- truly immutable objects are always thread-safe
- they help to avoid temporal coupling
- their usage is side-effect free (no defensive copies)
- identity mutability problem is avoided
- they always have failure atomicity
- they are much easier to cache
- they prevent NULL references, which are bad
Let's discuss the most important arguments one by one.
The first and the most obvious argument is that immutable objects are thread-safe. This means that multiple threads can access the same object at the same time, without clashing with another thread.
If no object methods can modify its state, no matter how many of them and how often are being called parallel — they will work in their own memory space in stack.
Goetz et al. explained the advantages of immutable objects in more details in their very famous book Java Concurrency in Practice (highly recommended).
Avoiding Temporal Coupling
Here is an example of temporal coupling (the code makes two consecutive HTTP POST requests, where the second one contains HTTP body):
This code works. However, you must remember that the first request should be configured before the second one may happen. If we decide to remove the first request from the script, we will remove the second and the third line, and won't get any errors from the compiler:
Now, the script is broken although it compiled without errors. This is what temporal coupling is about — there is always some hidden information in the code that a programmer has to remember. In this example, we have to remember that the configuration for the first request is also used for the second one.
We have to remember that the second request should always stay together and be executed after the first one.
Request class were immutable, the first snippet wouldn't work in the first place, and would have been rewritten like:
Now, these two requests are not coupled. We can safely remove the first one, and the second one will still work correctly. You may point out that there is a code duplication. Yes, we should get rid of it and re-write the code:
See, refactoring didn't break anything and we still don't have temporal coupling. The first request can be removed safely from the code without affecting the second one.
I hope this example demonstrates that the code manipulating immutable objects is more readable and maintainable, because it doesn't have temporal coupling.
Avoiding Side Effects
Let's try to use our
Request class in a new method (now it is mutable):
Let's try to make two requests — the first with GET method and the second with POST:
post() has a "side effect" — it makes changes to the mutable object
request. These changes are not really expected in this case. We expect it to make a POST request and return its body. We don't want to read its documentation just to find out that behind the scene it also modifies the request we're passing to it as an argument.
Needless to say, such side effects lead to bugs and maintainability issues. It would be much better to work with an immutable
In this case, we may not have any side effects. Nobody can modify our
request object, no matter where it is used and how deep through the call stack it is passed by method calls:
This code is perfectly safe and side effect free.
Avoiding Identity Mutability
Very often, we want objects to be identical if their internal states are the same.
Date class is a good example:
There are two different objects; however, they are equal to each other because their encapsulated states are the same. This is made possible through their custom overloaded implementation of
The consequence of this convenient approach being used with mutable objects is that every time we modify object's state it changes its identity:
This may look natural, until you start using your mutable objects as keys in maps:
When modifying the state of
date object, we're not expecting it to change its identity. We're not expecting to lose an entry in the map just because the state of its key is changed. However, this is exactly what is happening in the example above.
When we add an object to the map, its
hashCode() returns one value. This value is used by
HashMap to place the entry into the internal hash table. When we call
containsKey() hash code of the object is different (because it is based on its internal state) and
HashMap can't find it in the internal hash table.
It is a very annoying and difficult to debug side effects of mutable objects. Immutable objects avoid it completely.
Here is a simple example:
It is obvious that an object of class
Stack will be left in a broken state if it throws a runtime exception on overflow. Its
size property will be incremented, while
items won't get a new element.
Immutability prevents this problem. An object will never be left in a broken state because its state is modified only in its constructor. The constructor will either fail, rejecting object instantiation, or succeed, making a valid solid object, which never changes its encapsulated state.
For more on this subject, read Effective Java, 2nd Edition by Joshua Bloch.
Arguments Against Immutability
There are a number of arguments against immutability.
“Immutability is not for enterprise systems”. Very often, I hear people say that immutability is a fancy feature, while absolutely impractical in real enterprise systems. As a counter-argument, I can only show some examples of real-life applications that contain only immutable Java objects: jcabi-http, jcabi-xml, jcabi-github, jcabi-s3, jcabi-dynamo, jcabi-w3c, jcabi-jdbc, jcabi-simpledb, jcabi-ssh. The above are all Java libraries that work solely with immutable classes/objects. netbout.com and stateful.co are web applications that work solely with immutable objects.
“It's cheaper to update an existing object than create a new one”. Oracle thinks that “The impact of object creation is often overestimated and can be offset by some of the efficiency associated with immutable objects. These include decreased overhead due to garbage collection, and the elimination of code needed to protect mutable objects from corruption.” I agree.
If you have some other arguments, please post them below and I'll try to comment.
P.S. Check takes.org, a Java web framework that consists entirely of immutable objects.