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.
1️⃣ All the fields in your class should be initialized only through the constructor.
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.
3️⃣ The class should be declared as final
so it cannot be extended by other classes and getting a chance to access the fields.
💯 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.
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.
❗️ 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
.
❗️ 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.
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.
Stay tuned! 🚀