An immutable object is an object that does not change its internal state after creation. They are very useful in multi-threaded applications because they can be shared between threads without synchronization.
To create an immutable object, you must follow a few simple rules:
1. Do not add installation method
If you build an immutable object, its internal state will never change. The goal of the setter method is to change the internal value of the field so that you cannot add it.
2. Declare all fields as final and closed.
A closed field is not visible outside the class, so no manual changes can be applied to it.
Declaring a final field ensures that if it refers to a primitive value, the value will never change; if it refers to an object, the link cannot be changed. This is not enough to guarantee that an object with only private trailing fields is not mutable.
3. If the field is a mutable object, create protective copies of it for receiving methods
We saw earlier that defining a final and private field is not enough, because you can change its internal state. To solve this problem, we need to create a protective copy of this field and return this field each time it is requested.
4. If the mutable object passed to the constructor should be assigned to the field, create a protective copy of it
The same problem arises if you hold the link passed to the constructor because it can be changed. Thus, keeping a reference to the object passed to the constructor, you can create mutable objects. To solve this problem, it is necessary to create a protective copy of the parameter if they are mutable objects.
Please note that if the field is a reference to an immutable object, there is no need to create its protective copies in the constructor and in the methods of obtaining, it is enough to define the field as final and closed.
5. Do not let subclasses override methods
If a subclass overrides a method, it can return the original value of the field being changed instead of its protective copy.
To resolve this problem, you can do one of the following:
- Declare the immutable class as final so that it cannot be extended
- Declare all methods of the immutable class final so that they cannot be overridden
- Create a private constructor and factory to instantiate an immutable class, because a class with private constructors cannot be extended
If you follow these simple rules, you can freely share your immutable objects between threads, because they are thread safe!
Below are a few more points of variability / immutability:
- Immutable objects really make life easier in many cases. They are especially applicable for value types where objects do not have an identity, so they can be easily replaced and they can make parallel programming safer and cleaner (most notoriously difficult to detect concurrency errors are ultimately caused by a mutable state shared between threads). However, for large and / or complex objects, creating a new copy of the object for each individual change can be very expensive and / or tedious . And for objects with an individual identity, changing existing objects is much simpler and more intuitive than creating a new modified copy.
- There are some things that you simply cannot do with immutable objects, such as having a bi-directional relationship . Once you set the association value for one object, its identity changes. So, you are setting a new value for another object, and it is also changing. The problem is that the first reference to the object is no longer valid because a new instance was created to represent the object with the link. Continuation of this will lead to endless regressions.
- To implement a binary search tree , you must return a new tree each time: your new tree will have to make a copy of each changed node (unchanged branches are shared). For your insert function, this is not so bad, but for me it all became pretty inefficient when I started working on removal and rebalancing.
- Hibernate and JPA essentially dictate that your system uses mutable objects because their whole premise is that they detect and save changes to your data objects.
- Depending on the language, the compiler can do a bunch of optimizations when working with immutable data, because it knows that the data will never change. All kinds of things are skipped, giving you a huge performance boost.
- If you look at other well-known JVM languages ββ( Scala, Clojure ), mutable objects are rarely found in code, and therefore people start using them in scripts where single-threaded is not enough.
There is no right or wrong, it just depends on what you prefer. It depends only on your preferences and what you want to achieve (and the ability to easily use both approaches without pushing the unwavering fans of either side is the holy grail that some languages ββstrive for).