Guide to Make Immutable Class in Java

In this guide, we will learn how to make Java class as immutable. Immutable objects are instances whose state doesn’t change after it has been initialized. For example, String is an immutable class and once instantiated its value never changes.
Below diagram shows the final String class from Java core library:
Immutable objects don’t expose any way for other objects to modify their state; the object’s fields are initialized only once inside the constructor and never change again.

Table of contents

  1. Definition of Immutable class.
  2. Usage of Immutable Classes.
  3. Rules to make a class immutable.
  4. Real World examples
  5. Example how to make class Immutable?
  6. Passing mutable objects to immutable class
  7. What are the advantages of immutability?

1. What is Immutable Class or Immutable Object?

An immutable class is simply a class whose instances cannot be modified. All of the information contained in each instance is provided when it is created and is fixed for the lifetime of the object.
For example, String is an immutable class and once instantiated its value never changes.
Once string object is initialized its value cannot be modified. Operations like trim(), substring(), replace() always return a new instance and don’t affect the current instance, that’s why we usually call trim() as the following:
String str = " JavaGuides ";
str = str .trim();
Read more about Strings at All String methods with examples.
Another example from JDK is the wrapper classes like Integer, Float, Boolean etc these classes don’t modify their state, however, they create a new instance each time you try to modify them.
Integer a = 6;
a += 6;
After calling a += 6, a new instance is created holding the value: 12 and the first instance is lost.
Read more about wrapper classes at All Java Wrapper Classes with Examples.

2. Usage of Immutable Classes

One major benefit of the immutable class is that it is inherently thread-safe, so you don’t need to worry about thread safety in case of multi-threaded environment.
With immutable classes, states are never modified; every modification of a state results in a new instance, hence each thread would use a different instance and developers wouldn’t worry about concurrent modifications.

3. Real-world examples

Check out few real-world examples of immutable classes in Java core library:
  • java.lang.String
  • The wrapper classes for the primitive types: java.lang.Integer, java.lang.Byte, java.lang.Character, java.lang.Short, java.lang.Boolean, java.lang.Long, java.lang.Double, java.lang.Float
  • java.lang.StackTraceElement (used in building exception stack traces).
  • java.math.BigInteger and java.math.BigDecimal (at least objects of those classes themselves, subclasses could introduce mutability, though this is not a good idea)
  • java.awt.Font - representing a font for drawing text on the screen (there may be some mutable subclasses, but this would certainly not be useful).
  • java.awt.BasicStroke - a helper object for drawing lines on graphics contexts.
  • java.awt.Color - (at least objects of this class, some subclasses may be mutable or depending on some external factors (like system colors)), and most other implementations of java.awt.Paint like  java.awt.GradientPaint, java.awt.LinearGradientPaint, java.awt.RadialGradientPaint.
  • java.awt.Cursor - representing the bitmap for the mouse cursor (here too, some subclasses may be mutable or depending on outer factors)
  • java.util.Locale - representing a specific geographical, political, or cultural region.
  • java.util.UUID -  globally unique identifier.

4. Rules to make a class immutable

  1. To make a class immutable, we need to follow these rules:
  2. Declare the class as final so it can’t be extended.
  3. Make all fields private so that direct access is not allowed.
  4. Don’t provide setter methods for variables
  5. Make all mutable fields final so that it’s value can be assigned only once.
  6. Initialize all the fields via a constructor performing a deep copy.
  7. Perform cloning of objects in the getter methods to return a copy rather than returning the actual object reference.

5. Example how to make class Immutable

Let's apply above rules and make the Complex class as an Immutable class. Note that this is a simple immutable class. In the next example, we will see different scenarios using immutable class.
public final class Complex {
    private final double re;
    private final double im;
    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
    // Accessors with no corresponding mutators
    public double realPart() {
        return re;
    }
    public double imaginaryPart() {
        return im;
    }
    public Complex add(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }
    public Complex subtract(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }
    public Complex multiply(Complex c) {
        return new Complex(re * c.re - im * c.im,
            re * c.im + im * c.re);
    }
    public Complex divide(Complex c) {
        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp,
            (im * c.re - re * c.im) / tmp);
    }
    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Complex))
            return false;
        Complex c = (Complex) o;
        // See page 43 to find out why we use compare instead of ==
        return Double.compare(re, c.re) == 0 &&
            Double.compare(im, c.im) == 0;
    }

    @Override
    public int hashCode() {
        int result = 17 + hashDouble(re);
        result = 31 * result + hashDouble(im);
        return result;
    }

    private int hashDouble(double val) {
        long longBits = Double.doubleToLongBits(re);
        return (int)(longBits ^ (longBits >>> 32));
    }

    @Override public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}
The above class is a very simple immutable class which doesn’t hold any mutable object and never expose its fields in any way; these type of classes are normally used for caching purposes.

6. Passing mutable objects to immutable class

Now, let’s complicate our example a bit, we create a mutable class called Address and add it as a field to Employee immutable class:
class Address implements Cloneable {
    private int id;
    private String address;


    public Address(int id, String address) {
        super();
        this.id = id;
        this.address = address;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Address [id=" + id + ", address=" + address + "]";
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
Note that Address class implements Clonable interface to make new instance of Address.
public class Employee {
    private final String name;
    private final int id;
    private final Address address;

    public Employee(String name, int id, Address address) throws CloneNotSupportedException {
        super();
        this.name = name;
        this.id = id;
        this.address = (Address) address.clone();
    }
    public String getName() {
        return name;
    }
    public int getId() {
        return id;
    }
    public Address getAddress() {
        return address;
    }


    @Override
    public String toString() {
        return "Employee  [name=" + name + ", id=" + id + ", address=" +
            address + "]";
    }
}
Note that in Employee constructor, we are calling clone method of Address class. There are two ways to handle this scenario either use Clonable interface or create a new instance address in Employee constructor.
Let's test above scenario with main() method:
public class EmployeeImutableObjectExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        final Address address = new Address(100, "pune");
        final Employee employee = new Employee("ramesh", 200, address);

        // Before changes to object
        System.out.println(" Before changes to object :: " + employee.toString());

        // Change the address object
        address.setAddress("mumbai");

        System.out.println(" After changes to object :: " + employee.toString());
    }
}
Output:
Before changes to object :: EmployeeImutableObjectExample [name=ramesh, id=200, address=Address [id=100, address=pune]]
After changes to object :: EmployeeImutableObjectExample [name=ramesh, id=200, address=Address [id=100, address=pune]]

7. What are the advantages of immutability?

  • Immutable objects are automatically thread-safe, the overhead caused due to use of synchronization is avoided.
  • Once created the state of the immutable object cannot be changed so there is no possibility of them getting into an inconsistent state.
  • The references to the immutable objects can be easily shared or cached without having to copy or clone them as their state cannot be changed ever after construction.
  • The best use of the immutable objects is as the keys of a map.

Related Java Tutorials


Comments