Two important design patterns/concepts in Java are:
- Singleton Class → ensures only one instance of a class exists.
- Immutable Class → ensures state cannot change after creation.
Both patterns improve memory efficiency, thread-safety, and program predictability, but they solve different problems.
A Singleton Class ensures:
- Exactly one instance of the class is created.
- A global access point to that instance is provided.
To implement a Singleton:
- Private constructor
- A static private reference of the class
- A public static method to return the instance
Instance created at class loading time.
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}- Simple and thread-safe
- Instance created even when not used
Instance created only when needed.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}- Not thread-safe
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
}- Slower due to method-level synchronization
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}- Uses volatile to prevent instruction reordering
- Considered best practice for multi-threaded environments
Uses static inner class.
public class Singleton {
private Singleton() {}
private static class Helper {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Helper.INSTANCE;
}
}- Lazy
- Thread-safe
- No synchronization overhead
public enum Singleton {
INSTANCE;
}- Serialization-safe
- Reflection-safe
- JVM guarantees only one instance
- Logging (Logger)
- Database connection pool
- Configuration managers
- Caches
- Thread pool managers
- Difficult to test (global state)
- Breaks dependency injection if overused
- Can be misused and lead to tight coupling
An Immutable Class is a class whose object cannot be modified after creation.
Example of immutable classes:
StringIntegerLocalDateBigDecimal
-
Make the class final → prevents subclassing
-
Make all fields private and final
-
No setters
-
Initialize fields via constructor
-
Prevent exposing mutable fields directly
- Use defensive copying for arrays/collections
public final class Employee {
private final String name;
private final int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}Object state cannot change:
Employee e = new Employee("John", 25);
// e.name = "David"; // ❌ cannot modifyHandling a mutable object (e.g., Date, List):
public final class Person {
private final String name;
private final Date dob;
public Person(String name, Date dob) {
this.name = name;
this.dob = new Date(dob.getTime()); // defensive copy
}
public String getName() { return name; }
public Date getDob() {
return new Date(dob.getTime()); // return copy
}
}| Feature | Singleton | Immutable |
|---|---|---|
| Purpose | Ensure one instance | Ensure unmodifiable objects |
| Instances | One only | Many allowed |
| Mutability | Can be mutable or immutable | Always immutable |
| Memory | Saves memory (single object) | Safe & thread-safe |
| Thread Safety | Requires careful implementation | Naturally thread-safe |
| Example | Logger, DB connection | String, Integer |
Yes.
Example:
public class Config {
private static final Config INSTANCE = new Config();
private final String version = "1.0";
private Config() {}
public static Config getInstance() {
return INSTANCE;
}
public String getVersion() { return version; }
}Here:
- Only one instance exists (singleton)
- Cannot modify state (immutable)
To ensure only one instance of a resource (database connection, logger) exists.
Use Enum Singleton or throw exception in constructor.
Implement:
protected Object readResolve() {
return instance;
}Better: use Enum Singleton.
Because their state cannot change once created → no data races.
- Security
- Class loader safety
- Thread-safety
- Hashing optimization
- String pool support
-
Singleton ensures only one instance exists globally.
-
Immutable class ensures object state cannot change.
-
Singleton may or may not be immutable.
-
Immutable objects are naturally thread-safe.
-
Enum-based singleton is the most robust implementation.
-
Defensive copying is essential when immutables contain mutable objects.