Understanding Immutability

By Waqqas Sheikh

In the programming world, Immutability is all the rage. Most new programming languages such as Swift, Kotlin, and Rust have first-class support for immutability while newer versions of older languages, such as EC6, are introducing features that make writing immutable code easier.

But what exactly is immutability and what benefits does it provide to Software Developers?

My goal in this article is to give a brief introduction to Immutability, go over the rules for an immutable object, and finally to investigate the pros and cons.

What exactly is immutability?

In object oriented programming, An immutable object is an object whose state, once initialized by the constructor, cannot be modified throughout the lifetime of the object.[1]

This essentially boils down to five simple rules:[1]

1.An immutable object does not expose methods that can change its state. An immutable object can, however, expose a method that returns a new immutable object with a modified state.

An example of this is String’s toUppercase() method which returns a new immutable instance of String with all the letters capitalised.

2. An immutable object can not be extended. This is to prevent subclasses from modifying the internal state. In Java, this can be done by marking the class as final.

3. All fields must be marked final. Java’s memory model ensures that an immutable object is passed correctly from one thread to another as long as it is immutable:

“final fields must be used correctly to provide a guarantee of immutability”

4. Immutable classes may not expose mutable fields, either through getters or through public fields.

Exposing a mutable property in an immutable class defeats the original intent.

Programmers need to take care to understand the classes that they’re working with. For example, Java’s ArrayList is mutable and returning a reference to it would result in a shoddy immutable object.

Arrays.asList() however returns an immutable List:

The Ups and The Downs

Now that we hopefully have an idea of what immutability is and how it’s implemented, I’d like to take a look at the pros and cons of working with immutable objects. Let’s start with the pros.

Benefits

As stated in the Java Language specification:

“compilers are allowed to keep the value of a final field cached in a register and not reload it from memory in situations where a non-final field would have to be reloaded.” [2]

2. State Machines

My personal favourite advantage of Immutability is state machines. A state machine is any system that, upon receiving an event, transitions from one known immutable state to another. This can be illustrated with a very simple example:

Here we have a mutable Task object. It describes a task, Laundry, which a customer, Joe Bloggs, wants to get done by a company called Cleaning Company.

Notice how both when the job is assigned and when it is completed, the developer forgot to update the job status field. This is a very common problem when working with mutable objects.

So now let’s see the immutable version:

In the immutable version, we immediately see that we have a state machine consisting of three immutable states: creation, assignment, and completion. To go from creation to assignment, a service provider must be provided. Likewise, to go from assignment to completion, a completion date is mandatory.

With the immutable version there is no room for error; a new developer who joins the team can instantly understand how Tasks work.

3. Thread Safety

Sharing mutable objects across threads can lead to inconsistent states. One thread can update the object according to one state, while another thread may update the object to another state resulting in an overall inconsistent state.

This can be demonstrated using the Task example above. Look back to the the mutable Task class defined above, now, imagine we had a UI where, when the user selects a service provider he sees an ‘Assign’ button. Once the ‘Assign’ button is pressed, it is replaced with a ‘Complete’ button.

An over-enthusiastic user might press the button very quickly so that assign and complete are pressed immediately one after the other.

Now supposing that both click listeners are handled on a separate thread but share the same Task object, we could have a race condition where one thread tries to set the service provider with the status ASSIGNED, while another tries to set the completion date with the status COMPLETED. This could result in a Task object with an inconsistent state e.g., both service provider and the completion date are set, but the status is still ASSIGNED.

This problem can be fixed by using locks and synchronization blocks, but this can get complicated for larger systems as the developer might end up having to debug deadlocks. Immutable classes offer a much cleaner solution:

Using immutable classes, saveTask always receives a consistent state of Task.

4. Pure Functions

A pure function is a function that receives inputs, processes them and yields an output without any side-effects to the surrounding system (or class). The simplest example is a function that receives two numbers as arguments and returns their product.

The benefit of pure functions is predictability: a pure function always returns a new value without modifying the old value avoiding any confusion.

We can see an our ImmutableTask snippets that all of the methods are pure methods since they receive inputs, process them to yield outputs without any side-effects to the enclosing class.

5. Easier Testing

As we saw in the previous Task example, we had three classes: CreatedTask, AssignedTask, and CompletedTask. Each class had four methods from the interface and one method to transition to the next state. That’s 15 methods in total to test the entire integrated flow from creation to completion.

Compare that with the the mutable Task version where even though there are only 5 methods to test in the class, there is no integration testing. Even if all 5 of those tests pass, it’s still possible to set the completion date without updating the job status.

Drawbacks

As we saw in the Task example above, one of the main disadvantages of Immutable objects is that you end up with a lot of boilerplate code.

In languages such as Java, this is an unavoidable drawback. Newer languages such as Swift, Kotlin, and Rust make defining immutable classes considerably less verbose. The Task example above could be simplified in Kotlin to:

2. Allocations Aplenty

As you might already have guessed, using immutable objects can result in a lot of allocations. However, immutable objects also allow for performance improvements because of their immutable nature.

For example, imagine a class that has a method, calculate(), which performs an intensive calculation using its fields. With a mutable object, the value of the fields in a class may change and as a result, the calculation may need to be repeated each time calculate() is called.

On the other hand, with an immutable object, the value of calculate() can be stored whilst initializing the object and returned whenever the result of the calculation is required.

This in fact, is precisely what the String class in java does with it’s hashcode. The hashcode is calculated and stored when the String is initialized. Subsequent calls to hashCode() simply return the pre-calculated value. This is an important optimization, considering strings are often used as keys in Maps.

Conclusion

Immutable objects are objects whose state does not change once they’ve been initialised nor do they expose methods that allow their state to be modified.

The main benefits they provide are simpler thread safety and the ability to write software in the form of state machines, which makes the code easier to understand and less error-prone.

Immutability does come with disadvantages however: there’s typically a lot of boilerplate involved in writing a proper immutable class. Additionally, immutability requires a lot of object allocation. These disadvantages can be resolved quite easily.

References

[1] Joshua Bloch, Effective Java 2nd Edition, Prentice Hall, 2004, p. 73.

[2] The Java Language Specification, Topic 17.5

About the author

Waqqas is an Agile Software Engineer at TribalScale, Dubai. He has 5 years of experience in developing Android Applications in the Middle East, and also develops backend APIs in Go.

Join TribalScale’s fast growing team and connect with us on Twitter, LinkedIn & Facebook! Learn more about us on our website.

Visit Us on Medium

You might also be interested in…