Immutability in Java

2022 01 02 head

Immutability is a powerful and simple concept in programming theory that is strangely underused.

An immutable object is simply a class whose instances cannot be modified. This is in contrast to a mutable object or changeable object, which can be modified after it is created.

Immutable objects are also useful because they are inherently thread-safe. Other benefits are that they are simpler to understand and reason about and offer higher security than mutable objects

All the information contained in each instance is provided when it is created and is fixed for the lifetime of the object.

An example of immutable objects in Java is the String class.

Why are immutable objects so good?

Advantages

There are many reasons for sure, here are the three main ones:

Protection Against Programming Errors

We can send an immutable object to any class without worrying about it being altered by that class. We never have to make a defensive copy. The same applies when we get an instance for local storage in a cache. We do not have to worry about whether the provider will hold on to a reference and change it later, invalidating our cache without our knowledge.

Increased Performance

We do not have to make defensive copies all the time. This means that we save some work on the garbage collector which increases performance and decreases memory overhead. We all want that, don’t we?

Thread Safety

After creation, any number of threads can access immutable objects simultaneously, without any synchronization mechanism. It is a huge advantage in the modern world of multicore processors.

Immutable Classes

  • The class must be declared as final. So those mutable child classes cannot be created.

  • Data members in the class must be declared as private. Direct access to member variables is prohibited.

  • Data members in the class must be declared as final. We cannot change the value of it after object creation. Java requires that all final properties must be initialized in the constructor.

  • No setters are provided to change the value of an instance variable.

  • A parameterized constructor should initialize all the fields performing a deep copy. Data members cannot be modified through external object reference. This is also called reference aliasing.

  • If the instance fields include references to mutable objects, do not allow those objects to be changed:

    • Do not provide methods that modify the mutable objects.

    • Do not share references to the mutable objects. Never store references to external, mutable objects passed to the constructor. If necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods.

Java record construct [1] implements most of the above constraints. The major limitation is that Java records do not perform any deep copies.

Records provide secure serialization and deserialization mechanisms. Use records as Java DTO objects and automatically improve the security of your application.

Therefore, Java records are shallow immutable objects. If you pass immutable objects to the constructor, you will achieve deep immutability.

Immutable API Classes

Java already has an extended and expanding set of immutable classes.

  • All wrapper classes in java.lang package are immutable. These are String, Character, Boolean, Byte, Short, Integer, Long, Float, Double.

  • The class java.awt.Rectangle encapsulates the position and dimension of a rectangle.

  • java.lang.StackTraceElement (used in building exception stack traces).

  • Most enum classes are immutable, but this in fact depends on the concrete case. Do not implement mutable enums, this will screw. All enum classes are immutable in the standard API.

  • java.math.BigInteger and java.math.BigDecimal,

  • java.io.File represents a file in a local or remote file system. A file may or may not exist, and has some methods modifying and querying the state of this external object. But the File object itself stays immutable.

  • java.util.Locale - representing a specific geographical, political, or cultural region,

Builder Pattern

Immutable abstractions have often verbose and cumbersome constructors. You want to provide a more elegant approach to create instances of your immutable classes. The builder pattern is ideal for creating immutable instances through legible and maintainable code.

Writing builders is a tedious and repetitive activity. Consider using the Lombok Project to generate builders. The lombok annotations @Builder and @Superbuilder are time savers.

Provide factory methods to create often used immutable objects. Use the builder in the body to write the creation code in one Java statement.

Read-only interface pattern

Read-only interface pattern is an alternative to the Immutable object pattern. It allows some objects to modify a value object while other objects can only fetch its values.