Header Ads Widget

Responsive Advertisement

Insight into Mutable and Immutable Class Structures

  

 

A   An Immutable Class with a Public Constructor

A class in Java is considered immutable if its instances are incapable of being modified post-creation. Once you create an object of an immutable class, you cannot change its state or fields. Immutable classes provide several benefits, including simplicity, thread-safety, and robustness.

Characteristics of an Immutable Class

  1. Final Class: The class should be declared as final to prevent subclassing, which could introduce mutability.
  2. Final Fields: All fields should be declared as final so their values cannot be changed after the object is constructed.
  3. No Setter Methods: Do not provide any methods that modify the state of the object.
  4. Initialization in Constructor: All fields should be initialized in the constructor.
  5. Defensive Copies: Make defensive copies of mutable fields and inputs.

Example of an Immutable Class

Let's consider an example of an immutable Person class:

java

public final class Person {

    private final String name;

    private final int age;

 

    // Constructor to initialize all fields

    public Person(String name, int age) {

        this.name = name;

        this.age = age;

    }

 

    // Getter methods

    public String getName() {

        return name;

    }

 

    public int getAge() {

        return age;

    }

 

    @Override

    public String toString() {

        return "Person{name='" + name + "', age=" + age + "}";

    }

 

    // Main method for testing

    public static void main(String[] args) {

        Person person = new Person("John", 25);

        System.out.println(person); // Output: Person{name='John', age=25}

 

        // Trying to change the state of the person object will fail

        // person.name = "Jane"; // Compilation error

        // person.age = 30;      // Compilation error

    }

}

 

Benefits of Immutable Classes

  1. Thread Safety: Immutable objects are inherently thread-safe. Multiple threads can safely access immutable objects without synchronization.
  2. Simplicity: Immutable objects are simpler to understand and use since their state does not change.
  3. Security: Immutable objects can be shared freely without risk of unintended changes.
  4. Cacheability: Immutable objects can be cached and reused, reducing the need for repeated object creation.

Defensive Copying

If your immutable class contains fields that refer to mutable objects, you need to make defensive copies of these objects to ensure immutability. Let's see an example:

java

import java.util.Date;

 

public final class Employee {

    private final String name;

    private final Date hireDate;

 

    // Constructor

    public Employee(String name, Date hireDate) {

        this.name = name;

        this.hireDate = new Date(hireDate.getTime()); // Defensive copy

    }

 

    // Getter methods

    public String getName() {

        return name;

    }

 

    public Date getHireDate() {

        return new Date(hireDate.getTime()); // Defensive copy

    }

 

    @Override

    public String toString() {

        return "Employee{name='" + name + "', hireDate=" + hireDate + "}";

    }

 

    // Main method for testing

    public static void main(String[] args) {

        Date date = new Date();

        Employee employee = new Employee("John", date);

        System.out.println(employee); // Output: Employee{name='John', hireDate=<current date>}

 

        // Attempt to modify the date outside the Employee class

        date.setTime(date.getTime() + 1000000000L);

        System.out.println(employee); // Output: Employee{name='John', hireDate=<unchanged date>}

    }

}

 

In this example, the Employee class has a hireDate field of type Date, which is mutable. To preserve immutability, the constructor and getter method create defensive copies of the Date object.

Summary

  1. Declare Class as Final: Prevents subclassing.
  2. Declare Fields as Final: Ensures fields cannot be reassigned after construction.
  3. No Setter Methods: Ensures fields cannot be modified after initialization.
  4. Initialize All Fields in Constructor: Ensures fields are set once during object creation.
  5. Defensive Copies: Prevent external modification of mutable fields by copying them.

By following these principles, you can create immutable classes that are robust, thread-safe, and easy to reason about.

 


 

B   An Immutable Class with a Private Constructor

Creating an immutable class in Java with a private constructor is a design pattern often used to provide additional control over the creation of instances. This pattern can be useful for enforcing immutability and providing factory methods or builders for object creation. Below, I will explain the concept in depth and provide examples.

Characteristics of an Immutable Class with a Private Constructor

  1. Final Class: The class should be declared as final to prevent subclassing.
  2. Final Fields: All fields should be declared as final to ensure they cannot be changed after initialization.
  3. Private Constructor: The constructor is private to prevent direct instantiation.
  4. Factory Methods: Public static factory methods or builders are provided for object creation.
  5. No Setter Methods: The class should not have any methods that modify the state of the object.
  6. Defensive Copies: If the class holds references to mutable objects, defensive copies should be made.

Example of an Immutable Class with a Private Constructor

Let's create an immutable Person class with a private constructor and a factory method:

java

public final class Person {

    private final String name;

    private final int age;

 

    // Private constructor

    private Person(String name, int age) {

        this.name = name;

        this.age = age;

    }

 

    // Public factory method

    public static Person createPerson(String name, int age) {

        return new Person(name, age);

    }

 

    // Getter methods

    public String getName() {

        return name;

    }

 

    public int getAge() {

        return age;

    }

 

    @Override

    public String toString() {

        return "Person{name='" + name + "', age=" + age + "}";

    }

 

    // Main method for testing

    public static void main(String[] args) {

        Person person = Person.createPerson("John", 25);

        System.out.println(person); // Output: Person{name='John', age=25}

    }

}

 

Explanation

  1. Final Class: Declared as final to prevent subclassing.
  2. Final Fields: All fields are final to ensure they cannot be reassigned.
  3. Private Constructor: The constructor is private to control object creation.
  4. Factory Method: A public static factory method (createPerson) is provided to create instances of the class.
  5. No Setter Methods: The class does not have any methods that modify its state after creation.

Benefits of Using a Private Constructor with Factory Methods

  1. Control Over Object Creation: By using a private constructor and factory methods, you have more control over the creation process. You can perform additional validation or pre-processing if necessary.
  2. Encapsulation: The internal representation of the class is fully encapsulated. This can help maintain immutability and hide the complexity of object creation.
  3. Named Constructors: Factory methods can have meaningful names that describe the creation process, making the code more readable.

Example with Defensive Copying

To guarantee immutability in a class with mutable fields, it is crucial to utilize defensive copying:

java

import java.util.Date;

 

public final class Employee {

    private final String name;

    private final Date hireDate;

 

    // Private constructor

    private Employee(String name, Date hireDate) {

        this.name = name;

        this.hireDate = new Date(hireDate.getTime()); // Defensive copy

    }

 

    // Public factory method

    public static Employee createEmployee(String name, Date hireDate) {

        return new Employee(name, hireDate);

    }

 

    // Getter methods

    public String getName() {

        return name;

    }

 

    public Date getHireDate() {

        return new Date(hireDate.getTime()); // Defensive copy

    }

 

    @Override

    public String toString() {

        return "Employee{name='" + name + "', hireDate=" + hireDate + "}";

    }

 

    // Main method for testing

    public static void main(String[] args) {

        Date date = new Date();

        Employee employee = Employee.createEmployee("John", date);

        System.out.println(employee); // Output: Employee{name='John', hireDate=<current date>}

 

        // Attempt to modify the date outside the Employee class

        date.setTime(date.getTime() + 1000000000L);

        System.out.println(employee); // Output: Employee{name='John', hireDate=<unchanged date>}

    }

}

 

In this example, the Employee class has a hireDate field of type Date, which is mutable. The constructor and getter method create defensive copies of the Date object to preserve immutability.

Summary

  1. Final Class: Prevents subclassing.
  2. Final Fields: Ensures fields cannot be reassigned after initialization.
  3. Private Constructor: Controls object creation.
  4. Factory Methods: Provides a controlled way to create instances, allowing for additional processing or validation.
  5. No Setter Methods: Maintains immutability by preventing state changes after object creation.
  6. Defensive Copies: Ensures references to mutable objects do not compromise immutability.

By following these principles, you can create immutable classes with private constructors that are robust, thread-safe, and easy to reason about.


 

      C.   An Immutable Class with a Synchronization

Immutable classes in Java are inherently thread-safe because their state cannot be changed after they are created. This eliminates the need for synchronization when sharing immutable objects across multiple threads. However, understanding the relationship between immutability and synchronization, and knowing when and how to combine them, is important for writing robust concurrent programs.

Characteristics of Immutable Classes

  1. Final Class: Prevent subclassing by declaring the class as final.
  2. Final Fields: All fields are declared as final to ensure they cannot be reassigned after initialization.
  3. No Setter Methods: Do not provide methods that modify the state of the object.
  4. Initialization in Constructor: All fields are initialized within the constructor.
  5. Defensive Copies: Create defensive copies of mutable fields and inputs.

Understanding Synchronization and Immutability

  1. Thread-Safety: Immutable objects are thread-safe by design. Since their state cannot change, they can be safely shared between threads without synchronization.
  2. Synchronization for Mutable Objects: When dealing with mutable objects, synchronization is often required to prevent concurrent modifications and ensure visibility of changes across threads.
  3. Combining Immutability and Synchronization: Even though immutable objects do not require synchronization, synchronization can be used in a broader design to handle the creation and sharing of immutable objects in a thread-safe manner.

Example of an Immutable Class

This text presents an example of a Person class characterized by its immutability:

java

public final class Person {

    private final String name;

    private final int age;

 

    // Constructor to initialize all fields

    public Person(String name, int age) {

        this.name = name;

        this.age = age;

    }

 

    // Getter methods

    public String getName() {

        return name;

    }

 

    public int getAge() {

        return age;

    }

 

    @Override

    public String toString() {

        return "Person{name='" + name + "', age=" + age + "}";

    }

 

    // Main method for testing

    public static void main(String[] args) {

        Person person = new Person("John", 25);

        System.out.println(person); // Output: Person{name='John', age=25}

    }

}

 

Synchronization in Immutable Object Creation

In some cases, you might need synchronization to control the creation and sharing of immutable objects. For instance, if an immutable object is created lazily or shared through a mutable reference, synchronization ensures thread-safety during these operations.

Singleton Pattern with Immutable Object

The Singleton pattern is a common use case where synchronization is combined with immutability:

java

public final class ImmutableSingleton {

    private static volatile ImmutableSingleton instance;

    private final String value;

 

    // Private constructor

    private ImmutableSingleton(String value) {

        this.value = value;

    }

 

    // Public factory method with double-checked locking

    public static ImmutableSingleton getInstance(String value) {

        if (instance == null) {

            synchronized (ImmutableSingleton.class) {

                if (instance == null) {

                    instance = new ImmutableSingleton(value);

                }

            }

        }

        return instance;

    }

 

    // Getter method

    public String getValue() {

        return value;

    }

 

    @Override

    public String toString() {

        return "ImmutableSingleton{value='" + value + "'}";

    }

 

    // Main method for testing

    public static void main(String[] args) {

        ImmutableSingleton singleton = ImmutableSingleton.getInstance("Initial Value");

        System.out.println(singleton); // Output: ImmutableSingleton{value='Initial Value'}

    }

}

 

Explanation

  1. Double-Checked Locking: Ensures that synchronization is only used during the first initialization of the singleton instance.
  2. Volatile Keyword: Ensures visibility of the instance variable across threads.

Combining Immutability with Synchronization for Mutable State

In some designs, you may need to manage mutable state while ensuring certain parts remain immutable. Here's an example using an immutable configuration object:

java

public final class Configuration {

    private final String databaseUrl;

    private final int maxConnections;

 

    // Private constructor

    private Configuration(String databaseUrl, int maxConnections) {

        this.databaseUrl = databaseUrl;

        this.maxConnections = maxConnections;

    }

 

    // Factory method

    public static Configuration create(String databaseUrl, int maxConnections) {

        return new Configuration(databaseUrl, maxConnections);

    }

 

    // Getter methods

    public String getDatabaseUrl() {

        return databaseUrl;

    }

 

    public int getMaxConnections() {

        return maxConnections;

    }

 

    @Override

    public String toString() {

        return "Configuration{databaseUrl='" + databaseUrl + "', maxConnections=" + maxConnections + "}";

    }

}

class ConfigManager {

    private volatile Configuration configuration;

 

    public synchronized void updateConfiguration(String databaseUrl, int maxConnections) {

        this.configuration = Configuration.create(databaseUrl, maxConnections);

    }

 

    public Configuration getConfiguration() {

        return configuration;

    }

 

    // Main method for testing

    public static void main(String[] args) {

        ConfigManager manager = new ConfigManager();

        manager.updateConfiguration("jdbc:mysql://localhost:3306/mydb", 10);

 

        Configuration config = manager.getConfiguration();

        System.out.println(config); // Output: Configuration{databaseUrl='jdbc:mysql://localhost:3306/mydb', maxConnections=10}

    }

}

 

Explanation

  1. Immutable Configuration Object: The Configuration class is immutable.
  2. Volatile and Synchronized Methods: The ConfigManager class uses synchronization to safely update the configuration and volatile to ensure visibility.

Summary

  1. Immutable Objects: Provide thread-safety by design and can be shared freely between threads.
  2. Synchronization for Creation: Use synchronization to control the creation and sharing of immutable objects when necessary.
  3. Combining Immutability and Synchronization: Use patterns like Singleton and encapsulated mutable state to leverage the benefits of both immutability and synchronization.

By understanding and applying these principles, you can design robust, thread-safe applications that leverage the benefits of immutability and synchronization effectively.

 

Some other examples:

Step 1>

package com.kartik.immutable;

import java.util.Date;

public class TestMain {

 public static void main(String[] args)

    {

        Immutable im = Immutable.createNewInstance(100,"test", new Date(),"");

        System.out.println(im);

       tryUpdate(im.getImField1(),im.getImField2(),im.getMuField()); 

        System.out.println(im);

      }

    private static void tryUpdate(Integer imField1, String imField2, Date muField)

    {

        imField1 = 100;

        imField2 = "Mandal changed";

        muField.setTime(20);

    }

}

 

Step 2>

package com.kartik.immutable; 

import java.util.Date;

public final class Immutable {

  /**

     * Due to the absence of setter methods, the Integer class is considered immutable, meaning its content cannot be altered.

     * */

     private final Integer imField1;

     /**

     * As an immutable class, String does not provide setters that would allow for changes to its content

     * */

     private final String imField2;

     /**

     * As the Date class is mutable, it provides setter methods that permit adjustments to various aspects of the date and time

     * */

     private final Date muField;

     /**

      * name is by default final, So no chnages

      * */

  private final String name="kartik";

     /**

   * @return the name

   */

  public String getName() {

   return name;

  }

  // Implementing a default private constructor guarantees that the class cannot be instantiated unexpectedly

     private Immutable(Integer f1, String f2, Date date)

     {

         this.imField1 = f1;

         this.imField2 = f2;

         this.muField = new Date(date.getTime());

     }

 

     // The factory method pattern allows for the encapsulation of object creation logic within a single, dedicated area.

     public static Immutable createNewInstance(Integer f1, String f2, Date date, String name)

     {

         return new Immutable(f1, f2, date);

     }

     //Provide no setter methods

     /**

     * Since the Integer class is immutable, we can directly return the instance variable as it exists.

     * */

     public Integer getImField1() {

         return imField1;

     }

 

     /**

     * Since the String class is immutable, we can directly return the instance variable as it exists.

     * */

     public String getImField2() {

         return imField2;

     }

     /**

     * The Date class is mutable, which requires us to exercise caution. It is important not 

     * to return a reference to the original instance variable. Instead, we should return

    * a new Date object  that contains a copy of the original content.

     * */

     public Date getMuField() {

         return new Date(muField.getTime());

     }

     @Override

     public String toString() {

         return imField1 +" - "+ imField2 +" - "+ muField+" - "+name;

     }

}

 

Output:

100 - test - Mon Nov 30 20:16:00 IST 2015 - kartik

100 - test - Mon Nov 30 20:16:00 IST 2015 - kartik



Key Differences of Mutable vs Immutable

  1. State Modification:
    1. Mutable: The state can be changed after object creation.
    2. Immutable: The state cannot be changed after object creation.
  2. Setter Methods:
    1. Mutable: Typically provide setter methods to change the state.
    2. Immutable: Do not provide setter methods.
  3. Thread-Safety:
    1. Mutable: Not inherently thread-safe. Requires synchronization for safe concurrent access.
    2. Immutable: Inherently thread-safe. No synchronization required for concurrent access.
  4. Use Cases:
    1. Mutable: Suitable when objects need to change state frequently, such as in GUI applications or when modeling entities that naturally change over time.
    2. Immutable: Suitable for value objects, configuration settings, and cases where thread safety and simplicity are desired.

 


 

 

Mutable and Immutable Class
Mutable and Immutable Class


For More Java Related information, visit

Ø  Streams Lambdas Collectors and Functional Interfaces in Java 8

Ø  Java 8 support static method or default method or both in the interface

Ø  Inheritance Understand

Ø  Serialization understanding

Ø  Clone Under standing

Ø  Exception understanding

Ø  Garbage Collection Under Standing

Ø  How work Garbage Collection in Java

Ø  enum understand

 

For Other information, visit

Ø  How to get the neighbor of binary tree

Ø  OWASP (Open Web Application Security Project)

Ø  Mastering Debounce, Throttle, Rate Limit & Backoff in Java

Ø  How to draw sequence diagram and other diagrams using plantuml

Ø  Pascal Triangle

Ø  Molecular weight of chemistry in Java code

Ø  String to xml or html Beautifier

Ø  Key Components of Apache Kafka for Scalable Messaging

Ø  Build a Video Stream Microservice with Kafka & REST API in Java

Ø  Kafka general questions and answers

Post a Comment

0 Comments