Java Interview Questions (Series) - Immutable Class

Don't change her, she don't wanna be changed! 🧩

Brief

At a certain point in most Java interviews, the conversation will naturally move towards the immutable word.

Either it's after talking about String and why is it immutable or when discussing HashMap internals.

At the end of this post you'll be much more comfortable answering the following questions and expanding on the topic.

🔸  What is an immutable class?

🔸  How would you create one?

🔸  What is it useful for?

🔸  Do you know examples of immutable Java classes?

Implementation

🔵   An immutable class is a class whose internals don't change over the lifecycle of the applications.

Rather than just enumerating the properties of it, let's do it a bit different.

🚀  We'll go from a simple class to an immutable class.

public class Dog {
    public String name;
}
Dog.java

1️⃣   All the fields in your class should be initialized only through the constructor.

public class Dog {
    public String name;
        
    public Dog(String name) {
        this.name = name;
    }
}
Dog.java

2️⃣   The inner fields should also be declared as final  and private without setter methods for them.

This way they can't be changed once initialized and should be accesible only through getter methods.

public class Dog {
    private final String name;
        
    public Dog(String name) {
        this.name = name;
    }
    
    public String getName() {
        return this.name;
    }
}
Dog.java

3️⃣  The class should be declared as final so it cannot be extended by other classes and getting a chance to access the fields.

public final class Dog {
    private final String name;
        
    public Dog(String name) {
        this.name = name;
    }
    
    public String getName() {
        return this.name;
    }
}
Dog.java

💯   At this point we have an immutable class.


❓  Now, what if we want to add a new field of a custom type?

🔵  Say we want to know the birth place of the dog.  Let's create a simple address class.

public class Address {
    public String streetName;
    public String streetNo;

    public Address(String streetName, String streetNo) {
        this.streetName = streetName;
        this.streetNo = streetNo;
    }
}
Address.java

We add it as a field inside the Dog class.

In order to keep our class immutable, we need to declare the field as private and final and have a getter method to retrieve the address details.

public final class Dog {
    private final String name;
    private final Address birthPlace;
        
    public Dog(String name, Address birthPlace) {
        this.name = name;
        this.birthPlace = birthPlace;
    }
    
    public String getName() {
        return this.name;
    }
    
    public Address getBirthPlace() {
        return this.birthPlace;
    }
}
Dog.java

❗️  We might think that's it. We would be wrong!

But how could that be? We did everything just the same as for the name field.

To understand why, let's take a look at an example where we use the Dog class.

public class ImmutableClassApplication {

    public static void main(String[] args) {

        Dog dog = new Dog("Max", new Address("Oxford", "23A"));

        dog.getBirthPlace().streetName = "Bingham";  // change the street of the dog's birth place.

        System.out.println("Dog name: " + dog.getName() 
        + "\n Street: " + dog.getBirthPlace().getStreetName()
        + "\n Number: " + dog.getBirthPlace().getStreetNo());
    }

}

Output:

Dog name: Max
Street: Bingham
Number: 23A

❗️  As you can see, we can actually update the value of the birthPlace field and implicitly change the dog object.

Because of that, besides having inner fields declared as private and final and not defining setters, you should also take a look on your inner fields to be immutable.

❗️  The difference between the two fields is that String is declared implicitly immutable in Java.

Have a look on this post to understand a lot more on what happens behinds the scene with a String!

🧩 Besides String, more predefined classes are immutable in Java, like Integer and Long and so on.

🔵  Having said that, the solution is to change the Address class to make it immutable.

public final class Address {
    private final String streetName;
    private final String streetNo;

    public Address(String streetName, String streetNo) {
        this.streetName = streetName;
        this.streetNo = streetNo;
    }

    public String getStreetName() {
        return streetName;
    }

    public String getStreetNo() {
        return streetNo;
    }
}
Address.java

❗️  This solution is viable when we can actually change the custom type to make it immutable.

But what if we don't want to make the Address class immutable, because of other dependencies on it. What can we do now?

🔵  The solution in this case is to retrieve a clone of the object in the particular getter method.

1️⃣   To do that, we need to add a new constructor to the Address class, receiving an Address object as a parameter.

public final class Address {
    private final String streetName;
    private final String streetNo;

    public Address(String streetName, String streetNo) {
        this.streetName = streetName;
        this.streetNo = streetNo;
    }
    
    // clone constructor
    public Address(Address address) {
        this.streetName = address.getStreetName();
        this.streetNo = address.getStreetNo();
    }

    public String getStreetName() {
        return streetName;
    }

    public String getStreetNo() {
        return streetNo;
    }
}

2️⃣   Now, when we need to get the dog's birthPlace we will retrieve a clone of that object.

public final class Dog {
    private final String name;
    private final Address birthPlace;
        
    public Dog(String name, Address birthPlace) {
        this.name = name;
        this.birthPlace = birthPlace;
    }
    
    public String getName() {
        return this.name;
    }
    
    public Address getBirthPlace() {
        return new Address(this.birthPlace);
    }
}

3️⃣  After this change, running the previous code, will have a different output.

public class ImmutableClassApplication {

    public static void main(String[] args) {

        Dog dog = new Dog("Max", new Address("Oxford", "23A"));

        dog.getBirthPlace().streetName = "Bingham";  // the change will be done on the clone and not the actual value of the dog's birth place

        System.out.println("Dog name: " + dog.getName() 
        + "\n Street: " + dog.getBirthPlace().getStreetName()
        + "\n Number: " + dog.getBirthPlace().getStreetNo());
    }

}
ImmutableClassApplication.java

Output:

Dog name: Max
Street: Oxford
Number: 23A


❓  Now that we know how to create an immutable and what to be on the lookout for, what is the actual purpose of it?

🧩  The most common usages are:

1️⃣   Have a custom class used as a key in a Map.

    🔹 The reason for this is that the hash of an immutable object will always be the same and would work a lot better in terms of performance.

2️⃣   Hold values on a multi-threaded application.

     🔹 Because you don't need to worry about different threads changing the internal state of the object.


💡
Don't miss out on more posts like this! Susbcribe to our free newsletter!
💡
I am currently working on a Java Interview e-book designed to successfully get you through any Java technical interview you may take.
Stay tuned! 🚀