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
- Final
Class: The class should be declared as final to prevent subclassing,
which could introduce mutability.
- Final
Fields: All fields should be declared as final so their values cannot
be changed after the object is constructed.
- No
Setter Methods: Do not provide any methods that modify the state of
the object.
- Initialization
in Constructor: All fields should be initialized in the constructor.
- 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
- Thread
Safety: Immutable objects are inherently thread-safe. Multiple threads
can safely access immutable objects without synchronization.
- Simplicity:
Immutable objects are simpler to understand and use since their state does
not change.
- Security:
Immutable objects can be shared freely without risk of unintended changes.
- 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
- Declare
Class as Final: Prevents subclassing.
- Declare
Fields as Final: Ensures fields cannot be reassigned after
construction.
- No
Setter Methods: Ensures fields cannot be modified after
initialization.
- Initialize
All Fields in Constructor: Ensures fields are set once during object
creation.
- 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
- Final
Class: The class should be declared as final to prevent subclassing.
- Final
Fields: All fields should be declared as final to ensure they cannot
be changed after initialization.
- Private
Constructor: The constructor is private to prevent direct
instantiation.
- Factory
Methods: Public static factory methods or builders are provided for
object creation.
- No
Setter Methods: The class should not have any methods that modify the
state of the object.
- 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
- Final
Class: Declared as final to prevent subclassing.
- Final
Fields: All fields are final to ensure they cannot be reassigned.
- Private
Constructor: The constructor is private to control object creation.
- Factory
Method: A public static factory method (createPerson) is provided to
create instances of the class.
- 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
- 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.
- Encapsulation:
The internal representation of the class is fully encapsulated. This can
help maintain immutability and hide the complexity of object creation.
- 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
- Final
Class: Prevents subclassing.
- Final
Fields: Ensures fields cannot be reassigned after initialization.
- Private
Constructor: Controls object creation.
- Factory
Methods: Provides a controlled way to create instances, allowing for
additional processing or validation.
- No
Setter Methods: Maintains immutability by preventing state changes
after object creation.
- 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
- Final
Class: Prevent subclassing by declaring the class as final.
- Final
Fields: All fields are declared as final to ensure they cannot be
reassigned after initialization.
- No
Setter Methods: Do not provide methods that modify the state of the
object.
- Initialization
in Constructor: All fields are initialized within the constructor.
- Defensive
Copies: Create defensive copies of mutable fields and inputs.
Understanding Synchronization and Immutability
- Thread-Safety:
Immutable objects are thread-safe by design. Since their state cannot
change, they can be safely shared between threads without synchronization.
- Synchronization
for Mutable Objects: When dealing with mutable objects,
synchronization is often required to prevent concurrent modifications and
ensure visibility of changes across threads.
- 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
- Double-Checked
Locking: Ensures that synchronization is only used during the first
initialization of the singleton instance.
- 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
- Immutable
Configuration Object: The Configuration class is immutable.
- Volatile
and Synchronized Methods: The ConfigManager class uses synchronization
to safely update the configuration and volatile to ensure visibility.
Summary
- Immutable
Objects: Provide thread-safety by design and can be shared freely
between threads.
- Synchronization
for Creation: Use synchronization to control the creation and sharing
of immutable objects when necessary.
- 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
- State
Modification:
- Mutable:
The state can be changed after object creation.
- Immutable:
The state cannot be changed after object creation.
- Setter
Methods:
- Mutable:
Typically provide setter methods to change the state.
- Immutable:
Do not provide setter methods.
- Thread-Safety:
- Mutable:
Not inherently thread-safe. Requires synchronization for safe concurrent
access.
- Immutable:
Inherently thread-safe. No synchronization required for concurrent
access.
- Use
Cases:
- Mutable:
Suitable when objects need to change state frequently, such as in GUI
applications or when modeling entities that naturally change over time.
- Immutable:
Suitable for value objects, configuration settings, and cases where
thread safety and simplicity are desired.
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
Ø
Serialization
understanding
Ø
Garbage
Collection Under Standing
Ø
How
work Garbage Collection in Java
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
Ø
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
0 Comments