An immutable class in Java is a class whose objects cannot be modified once created. This design ensures thread safety, simplifies concurrent programming, and provides better control over object states.
Steps to Create an Immutable Class
- Declare the Class as
final:- To prevent the class from being extended.
public final class ImmutableClass { // Class definition } - Make All Fields
privateandfinal:- Fields must be private to restrict direct access and final to ensure they cannot be reassigned after initialization.
private final String name; private final int age; - Initialize Fields via Constructor:
- Provide a parameterized constructor to initialize all fields.
public ImmutableClass(String name, int age) { this.name = name; this.age = age; } - Do Not Provide Setter Methods:
- Setter methods allow modification of fields, which is not allowed in immutable classes.
- Ensure Deep Copy of Mutable Objects:
- If the class contains mutable objects (e.g., collections), return a deep copy in getter methods.
private final List<String> hobbies; public ImmutableClass(String name, int age, List<String> hobbies) { this.name = name; this.age = age; this.hobbies = new ArrayList<>(hobbies); // Deep copy } public List<String> getHobbies() { return new ArrayList<>(hobbies); // Return a copy } - Provide Only Getter Methods:
- Provide methods to access the field values, but ensure they do not allow modification.
public String getName() { return name; } public int getAge() { return age; }
Complete Example of an Immutable Class
import java.util.ArrayList;
import java.util.List;
public final class ImmutableClass {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutableClass(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = new ArrayList<>(hobbies); // Deep copy
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getHobbies() {
return new ArrayList<>(hobbies); // Return a copy
}
public static void main(String[] args) {
List<String> hobbies = new ArrayList<>();
hobbies.add("Reading");
hobbies.add("Traveling");
ImmutableClass obj = new ImmutableClass("John", 30, hobbies);
System.out.println("Name: " + obj.getName());
System.out.println("Age: " + obj.getAge());
System.out.println("Hobbies: " + obj.getHobbies());
// Attempt to modify the hobbies list
hobbies.add("Swimming");
System.out.println("Hobbies after external modification: " + obj.getHobbies());
}
}
Output:
Name: John
Age: 30
Hobbies: [Reading, Traveling]
Hobbies after external modification: [Reading, Traveling]
Deep Copy vs. Shallow Copy
Shallow Copy:
- Definition: Creates a new object, but the fields of the object refer to the same memory addresses as the original object.
- Characteristics:
- Changes in mutable fields of the original object are reflected in the copied object.
import java.util.ArrayList;
import java.util.List;
public class ShallowCopyExample {
public static void main(String[] args) {
List<String> originalList = new ArrayList<>();
originalList.add("One");
originalList.add("Two");
List<String> shallowCopy = originalList; // References the same object
shallowCopy.add("Three");
System.out.println("Original List: " + originalList);
System.out.println("Shallow Copy: " + shallowCopy);
}
}
Deep Copy:
- Definition: Creates a new object and also copies the fields, ensuring that mutable fields are not shared.
- Characteristics:
- Changes in the original object do not affect the copied object.
import java.util.ArrayList;
import java.util.List;
public class DeepCopyExample {
public static void main(String[] args) {
List<String> originalList = new ArrayList<>();
originalList.add("One");
originalList.add("Two");
List<String> deepCopy = new ArrayList<>(originalList); // Creates a new object
deepCopy.add("Three");
System.out.println("Original List: " + originalList);
System.out.println("Deep Copy: " + deepCopy);
}
}
Other Copy Mechanisms
- Using Cloneable Interface:
- Java provides the
clone()method for creating object copies. - Requires the class to implement the
Cloneableinterface.
- Java provides the
class Example implements Cloneable {
int value;
public Example(int value) {
this.value = value;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CloneExample {
public static void main(String[] args) throws CloneNotSupportedException {
Example obj1 = new Example(10);
Example obj2 = (Example) obj1.clone();
System.out.println("Original: " + obj1.value);
System.out.println("Cloned: " + obj2.value);
}
}
2. Serialization and Deserialization:
- Serializing an object and then deserializing it creates a deep copy.
import java.io.*;
class Person implements Serializable {
String name;
public Person(String name) {
this.name = name;
}
}
public class SerializationExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person original = new Person("John");
// Serialize
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(original);
// Deserialize
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Person copy = (Person) ois.readObject();
System.out.println("Original: " + original.name);
System.out.println("Copy: " + copy.name);
}
}