Posted on: January 18, 2025 Posted by: rahulgite Comments: 0

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

  1. Declare the Class as final:
    • To prevent the class from being extended.
    public final class ImmutableClass { // Class definition }
  2. Make All Fields private and final:
    • 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;
  3. Initialize Fields via Constructor:
    • Provide a parameterized constructor to initialize all fields.
    public ImmutableClass(String name, int age) { this.name = name; this.age = age; }
  4. Do Not Provide Setter Methods:
    • Setter methods allow modification of fields, which is not allowed in immutable classes.
  5. 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 }
  6. 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

  1. Using Cloneable Interface:
    • Java provides the clone() method for creating object copies.
    • Requires the class to implement the Cloneable interface.
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);
   }
}

Leave a Comment