How To Create an Immutable Class in Java
Introduction
This article provides an overview of how to create an immutable class in Java programming.
An object is immutable when its state doesn’t change after it has been initialized. For example, String is an immutable class, and once instantiated, the value of a String object never changes.
Because an immutable object can’t be updated, programs need to create a new object for every change of state. However, immutable objects have the following benefits:
- Good for caching because you don’t have to worry about value changes.
- Inherently thread-safe, eliminating thread safety concerns in multi-threaded environments.
Create an Immutable Class in Java
To create a class in Java, follow these principles:
- Declare the class as
final
so it can’t be extended. - Make all fields
private
to restrict direct access. - Don’t provide setter methods for variables.
- Make mutable fields
final
so they can only be assigned once. - Initialize fields using a constructor that performs deep copy.
- Clone objects in getter methods to return copies, not the original object reference.
Example Code to Create an Immutable Class in Java
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
private final int id;
private final String name;
private final HashMap<String, String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
public FinalClassExample(int i, String n, HashMap<String, String> hm) {
System.out.println("Performing Deep Copy for Object initialization");
this.id = i;
this.name = n;
HashMap<String, String> tempMap = new HashMap<>();
Iterator<String> it = hm.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
tempMap.put(key, hm.get(key));
}
this.testMap = tempMap;
}
public static void main(String[] args) {
HashMap<String, String> h1 = new HashMap<>();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i = 10;
FinalClassExample ce = new FinalClassExample(i, s, h1);
System.out.println("ce id: " + ce.getId());
System.out.println("ce name: " + ce.getName());
System.out.println("ce testMap: " + ce.getTestMap());
i = 20;
s = "modified";
h1.put("3", "third");
System.out.println("ce id after local variable change: " + ce.getId());
System.out.println("ce name after local variable change: " + ce.getName());
System.out.println("ce testMap after local variable change: " + ce.getTestMap());
HashMap<String, String> hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from getter methods: " + ce.getTestMap());
}
}
Output
Performing Deep Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second}
ce testMap after changing variable from getter methods: {1=first, 2=second}
What Happens Without Deep Copy
When using shallow copy and returning objects directly:
public HashMap<String, String> getTestMap() {
return testMap;
}
The object is no longer immutable. Changes to the HashMap directly affect the object.
Example Output
Performing Shallow Copy for Object initialization
ce id: 10
ce name: original
ce testMap: {1=first, 2=second}
ce id after local variable change: 10
ce name after local variable change: original
ce testMap after local variable change: {1=first, 2=second, 3=third}
ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}
Conclusion to Create an Immutable Class
Creating immutable classes in Java requires careful design and adherence to specific principles for reliability. These principles include making the class final, ensuring all fields are private, and avoiding setters. Incorporating the use of deep copy is essential to prevent unintended modifications to mutable objects. This approach ensures thread safety, especially in multi-threaded environments, by avoiding race conditions. Immutable classes also preserve object integrity, making them predictable and easy to debug. Additionally, they are highly suitable for caching, as their state remains consistent throughout the program. By understanding and implementing these concepts, you can enhance your Java programming skills. Continue exploring advanced tutorials to deepen your knowledge and build better software.