Immutability
Generally, developers like setters.
After all, it stands to reason that if you can get a piece of data from an object, you should be able to modify that piece of data in that same object. We are used to read-write data structures, whether in memory or persisted.
However, there are some costs to allowing objects to be mutable (in other words, able to be modified). In some cases, you can have a more robust app architecture if you consider objects to be immutable and unable to be modified. The objects get replaced outright, rather than changing their contents. This winds up being a bit more reminiscent of a transactional database, where changes are applied as a unit, rather than piecemeal.
In this chapter, we will explore the benefits (and costs) of immutability and how to create immutable (or mostly immutable) objects in Java.
The Benefits of Immutability
Having immutable objects — particularly for things like models — is not a new concept. Immutability has had its adherents for quite some time. It is what lead to libraries like AutoValue for Java, and immutability features in languages like Kotlin.
So, why go with immutability?
No Dirty Data
A perpetual problem with model objects (and related objects, such as view-models), is knowing what data changed, when, and by what. That comes from promiscuous use of setters, blindly changing data that might be in use already.
For example:
- You implement a model cache, shared between your code that gets data from the server and your UI code, to minimize memory consumption
- You construct a view-model from a model object, representing data to be presented to the user in the UI
- You use two-way data binding, so user interactions with the UI directly update the view-model
- Your code for communicating with the server finds out about an update that happens to affect that same model object, and it updates the model object in the cache
- Your UI code, after the user clicks Save to commit the data changes, uses the view-model contents to update the model object
Now, we have possible data consistency issues:
- The model started in state A
- The view-model was created based on that state A
- The user mutates the view-model and moves it to state B
- The server-sync code mutates the model and moves it to state C
- The view-model updates the model and moves it to state B… potentially ignoring the changes the server-sync code made that resulted in state C
Immutable model and view-model objects do not prevent this sort of situation, but they help to make it a bit more obvious. Changing the state is a more obvious action; it is not merely a matter of calling some setters.
Thread Safety
If more than one thread has access to the same data, and that data can change, we wind up having to synchronize access to that data, so that changes can be made atomically. We do not want a thread to be part-way through updating the data when another thread tries reading it, as the partly-updated data may be in an inconsistent state at that moment. We wind up using synchronized
and CopyOnWriteArrayList
and all sorts of other constructs to allow mutable data to be shared between threads.
This goes away if the data is immutable. Multiple threads can read the same, unchanging data whenever they want without issue. Now, we limit our synchronization to more specific scenarios, such as updating shared caches: so long as we are replacing a cache entry atomically, all consumers of the cache can run in parallel, if the cache entries themselves are immutable.
Functional Programming
One way to combat the complexities of multi-threaded development is to use functional programming. Functional programming is based on pure functions: methods (or the equivalent) that operate solely on input parameters, with no side-effects that affect the operation of the program.
RxJava is based on functional programming concepts. We build up chains of RxJava operators, where each operator applies some sort of function to the input, such as the map()
operator applying a Function
to convert objects from one type to another.
Immutability is one way of imposing a contract upon yourself, as a developer, to avoid side effects. Calling a setter is a very casual act in programming, even if calling that setter introduces a side effect. Immutability enforces the creation of new objects, ideal for use in pure functions, where the function can create objects to return but cannot change the parameters’ contents and cause side effects.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.