Immutable Class In Java

There is a special type of class in java, whose object is once created, it cannot change its content. these are called Immutable classes in java. All the classes inside the java.lang package ie wrapper classes are immutable: ie String, Boolean, Byte, Character, Double, Float, Integer, Long, Short are immutable by default in java.

To understand the concept, let's take use java.lang.String as the immutable class and java.awt.Point as the mutable class.

Point myPoint = new Point(0, 0);
System.out.println(myPoint);
myPoint.setLocation(1.0, 1.0);
System.out.println(myPoint);

String myString = new String("Apple");
System.out.println(myString);
myString.replaceAll("Apple", "Orange");
System.out.println(myString);

it can be noticed that the contents of myPoint have changed, but the contents of myString did not.

A lot of functional programming languages use Immutibility. Languages like Haskell, OCaml and Scala have immutable-by-default approach for variables.

Advantage of Immutable Class

  • Immutable objects are by default thread-safe in java so they are easier to share between multiple threads without external synchronization. Immutable objects cannot be corrupted by multiple threads accessing them concurrently. This is the easiest approach to achieving thread safety.
  • Immutable objects are good candidates for caching because their value doesn't change.
  • In a multi-threaded environment, concurrency is solved by synchronization, but synchronization is a costly practice since Immutable objects don't change their state, hence reducing synchronization in code and increasing the performance of Java applications, that's why immutable objects are a great choice for shared objects in a multi-threaded environment.
  • If the user-defined object is required as the key of a map or set, Immutable objects are the best candidates.
  • Immutable objects make it easier to parallelize program as there are no conflicts among objects.

Disadvantage of Immutable Class

  • Since the immutable objects once created cannot be modified hence can not be reused which results in the creation of a lot of garbage objects. The string is an immutable object, hence every time contamination happens, it creates a new object.
  • If same immutable object is required with a slightly different value, then the entire variables need to copy into a new object.

Classes should be immutable unless there's a very good reason to make them mutable....If a class cannot be made immutable, limit its mutability as much as possible.

User-Defined Immutable Class

Java allows creating a user-defined class as immutable if a few points are taken care of.

final fields

Make all mutable fields final so that once values are assigned to them, they cannot be changed.

private fields

Make all data member fields private so that they cannot be accessed directly.

No Setter methods

Immutable class shout not have setter methods for variables, so they cannot be updated later.

final class

The class must be declared as final so it can’t be extended. This is sometimes referred to as Strong Immutability.

Another way is to make your methods final, it is also called Weak Immutability. It will protect the original methods but will allow other classes to extend the class and add more behaviour.

All Argument Constructor

All the fields will be Initialized by a constructor using Defensive Copies.

Static Factory method with private constructor

Another way is to have a private constructor and all the initialization using a static factory method. This sometimes helps to add caching of some immutable instances later.

Defensive copies of Getter Method
Perform cloning of objects in the getter methods to return a copy rather than returning the actual object reference.

 

From JDK 14+ which has JEP 359, records can be used. It is the simplest way of creating an Immutable class.

A record class is a shallowly immutable, transparent carrier for a fixed set of fields known as the record components that provides a state description for the record. Each component gives rise to a final field that holds the provided value and a accessor method to retrieve the value. The field name and the accessor name match the name of the component.

If all the above cases are taken care of properly, then the resultant class will be immutable, as below.

package org.wesome.dsalgo;

import java.util.Date;
import java.util.Objects;

/*  immutable class are final */
public final class Apple {
    private final String name;
    private final float price;
    private final Date date;

    /*  immutable class with method reference has defensive copies. */
    public Apple(String name, float price, Date date) {
        this.name = name;
        this.price = price;
        this.date = new Date(date.getYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
    }

    public String getName() {
        return name;
    }
    /*  immutable class don't have setter method */
    /*public void setName(String name) {
        this.name = name;
    }*/

    public float getPrice() {
        return price;
    }
    /*  immutable class don't have setter method */
   /* public void setPrice(float price) {
        this.price = price;
    }*/

    /*  immutable class with method reference has defensive copies. */
    public Date getDate() {
        return new Date(this.date.getYear(), this.date.getMonth(), this.date.getDate(), this.date.getHours(), this.date.getMinutes(), this.date.getSeconds());
    }

    /*  immutable class don't have setter method */
    /*public void setDate(Date date) {
        this.date = date;
    }*/

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Apple apple = (Apple) o;
        return Float.compare(apple.price, price) == 0 && Objects.equals(name, apple.name) && Objects.equals(date, apple.date);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, price, date);
    }

    @Override
    public String toString() {
        return "Apple{" + "name='" + name + '\'' + ", price=" + price + ", date=" + date + '}';
    }
}

follow us on