How to Create an Immutable Class in Java with Example

In order to create an object, we need to define a class that's why the class is called the blueprint of the object and an immutable class is a class which we can use to create immutable objects.

What is immutable object

An object is called immutable if its state cannot be modified by anyone in any way after its construction, here object's state means the fields or the variables it is holding.

An immutable object does not expose its state to the outer world and neither provides any behavior to modify its state. All wrapper classes i.e IntegerFloatLong are immutable in nature and other examples of immutable classes are String, java.util.UUID, java.net.URL.

Advantages of immutable objects

In Why String is Immutable and Final in Java, I have discussed how just by being immutable in nature, String gains lots of advantages including
  1. Thread Safety, 
  2. Hash-code caching,
  3. Object pool,
  4. Security.
Same advantages are applied to other immutable objects as well.

How to create a class for an immutable object

To create an immutable object we need to define our class in way that it restricts every one (including itself) from changing the state of the object after its construction, and in order to do so we need to
  1. Mark your class final,
  2. Mark all the fields private,
  3. Mark all fields final as well,
  4. Provide an argument constructor with all initialization logic,
  5. Initialize all mutable fields by deep copying,
  6. Do not provide setters for your fields,
  7. Return a deep copy of mutable fields from the getters.
How-to-Create-an-Immutable-Class-in-Java-with-Example

Let's look at all these rules and the reasons to follow them

1. Why mark our class final

We should declare our class final to forbids its extension so no one can extend our class and destroy its immutability. If it is not final then in future someone might extend it and modify the behavior to change the state.

2. Why mark all the fields private

We should mark all the fields private so no one can access them outside of the class.

3. Why mark all fields final as well

Mark all the fields final so even we will not be able to change the fields outside of the constructor.

4. Why provide an argument constructor with all initialization logic

A constructor is a place to write our object initialization logic because constructor gets called whenever we create an object.

So when we want to set our object's state during object creation only, we need to set it in the constructor and that's why we need to have an argument constructor in case of an immutable class.

As discussed in 5 different ways to create objects and creating objects through reflection, serialization and cloning also create new objects but both of them does not include a constructor call. But we do not need to worry about it because in both ways object will be constructed from an already present object which will be already immutable in nature.

5. Why initialize all mutable fields by deep copying

If our immutable object holds a reference to other immutable objects i.e. String, Integer we do not need to worry because we know they will not allow any change in their state.

But if our object holds references to some mutable objects and those mutable objects are also getting referred from somewhere else, in that case, our object's immutability is in danger.

In our example, our ImmutableEmployee class holds a reference to Date class which is mutable in nature. In below lines of code we are creating a variable dob which is holding a Date object and then we are passing it to ImmutableEmployee's constructor and creating an object which is being referred from employee.

Date dob = new Date();
ImmutableEmployee employee = new ImmutableEmployee(1, "Naresh", dob);

When we SysOut employee object we will get

ImmutableEmployee{id=1, name='Naresh', dob=Sun Jan 10 00:12:00 IST 1993}

Now if we do not initialize dob fields by deep copying then both dob and employee.dob will point to a single object and if we change anything in dob, employee.dob will also reflect that change which means employee object will become mutable.

But by deep copying dob field both employee.dob and dob will point to two different objects and we will not face this problem, as you can see output of below code

dob.setMonth(1);
System.out.println(dob); // Prints - Wed Feb 10 00:12:00 IST 1993

System.out.println(employee.getDob()); // Prints - Sun Jan 10 00:12:00 IST 1993
System.out.println(employee); // Prints - ImmutableEmployee{id=1, name='Naresh', dob=Sun Jan 10 00:12:00 IST 1993}

In our example, I have used copy constructor this.dob = new Date(dob.getTime()); to copy our objects because there are some basic problems with Java cloning and we can not sure of either it is a deep copy or shallow copy without seeing cloning code of that class.

You can read more about cloning, cloning types and why copy constructors are better than on more detailed articles such as Java Cloning And Types Of Cloning (Shallow and Deep), Java Cloning - Copy Constructor Versus Cloning and Java Cloning - Even Copy Constructors Are Not Sufficient.

6. Why should not provide setters for your fields

Well, providing setters will allow us to modify the state of the object which we do not want.

7. Why return a deep copy of mutable fields instead of returning objects from the getters.

If we return all mutable fields directly, we will face the same scenario as discussed in point 5 and after executing below code both employee.dob and temp will point to the same object, now if we make any change in temp, employee.dob will also change which again means employee will not remain immutable.

So instead of returning mutable fields, we should return their deep copy and as we have done that, we can see in below code employee remains same and immutable at the end.

Date temp = employee.getDob();
temp.setMonth(2);
System.out.println(temp); // Prints - Wed Mar 10 00:12:00 IST 1993

System.out.println(employee.getDob()); // Prints - Sun Jan 10 00:12:00 IST 1993
System.out.println(employee); // Prints - ImmutableEmployee{id=1, name='Naresh', dob=Sun Jan 10 00:12:00 IST 1993}

In the end, I wanted to say that all immutable objects in Java are effective immutable not completely immutable because we can modify them using reflection.

Below is the complete source code to create an immutable class which you can also find on this Github Repository and please feel free to provide your valuable feedback.

// 1. Declare your class as final, So other classes can't extend it and break its immutability
final class ImmutableEmployee {

    // 2. Make all your fields private they can't be accessed outside your class
    // 3. Mark them as final so no one can modify them anywhere else apart from the constructor, if you do not have any specific requirement to not do so
    private final int id;
    private final String name;
    private final Date dob;

    // 4. Create an constructor with argument so you can assign instantiate your object with a proper state
    public ImmutableEmployee(int id, String name, Date dob) {
        this.id = id;
        this.name = name;
        // 5. Initialise all your fields by deeply copying them if they are not immutable in nature
        this.dob = new Date(dob.getTime());
    }

    // 6. Do not provide setters for your fields, or define them private if you have some requirement
    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    // 7. Instead of returning objects from the getters return deep copy them if your objects are not immutable
    public Date getDob() {
        return new Date(dob.getTime());
    }

    @Override
    public String toString() {
        return "ImmutableEmployee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", dob=" + dob +
                '}';
    }

}

public class ImmutableClassExample {

    public static void main(String[] args) throws ParseException {
        Date dob = new SimpleDateFormat("dd-mm-yyyy").parse("10-12-1993");
        ImmutableEmployee employee = new ImmutableEmployee(1, "Naresh", dob);

        System.out.println(employee); // Prints - ImmutableEmployee{id=1, name='Naresh', dob=Sun Jan 10 00:12:00 IST 1993}

        dob.setMonth(1);
        System.out.println(dob); // Prints - Wed Feb 10 00:12:00 IST 1993

        Date temp = employee.getDob();
        temp.setMonth(2);
        System.out.println(temp); // Prints - Wed Mar 10 00:12:00 IST 1993

        System.out.println(employee.getDob()); // Prints - Sun Jan 10 00:12:00 IST 1993
        System.out.println(employee); // Prints - ImmutableEmployee{id=1, name='Naresh', dob=Sun Jan 10 00:12:00 IST 1993}
    }
}


Previous Post Older Post Home

1 comment :

  1. This comment has been removed by a blog administrator.

    ReplyDelete