Immutable Class
An immutable class in Java is a class whose objects cannot be modified after they are created. Once an instance of an immutable class is initialized, its state remains unchanged throughout its lifetime. Immutable classes are useful for ensuring thread safety and preventing unintended modifications.
Key Characteristics of Immutable Classes
- Final Class – The class is declared as
final
to prevent subclassing. - Final Fields – All fields are declared
private
andfinal
so they can’t be modified after object creation. - No Setter Methods – There are no methods that allow changing the fields after initialization.
- Constructor Initialization – All fields are initialized through the constructor.
- Deep Copies – If the class has mutable objects, it returns defensive copies instead of direct references.
Example of an Immutable Class
final class ImmutablePerson {
private final String name;
private final int age;
// Constructor
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
// Getter methods (No setters)
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Why Use Immutable Classes?
- Thread Safety – Immutable objects can be safely shared across multiple threads without synchronization.
- Caching & Performance – They can be reused since their state doesn’t change.
- Security – Immutable classes prevent unintended modifications, making them more predictable and reliable.
Java has built-in immutable classes like
String
,
Integer
, and
BigDecimal
. If you need any more details, let me know!
- Declare the Class as
final
- This prevents direct access and ensures fields cannot be modified after initialization.
- Make All Fields
private
andfinal
- Prevents direct access and ensures fields cannot be modified after initialization.
- Initialize Fields Through a Constructor
- Assign values only once when the object is created.
- Do Not Provide
Setter
Methods- Setters allow modification, which breaks immutability.
- Return Defensive Copies of Mutable
Objects (if any)
- If your class has fields that are references to mutable objects, return a new copy instead of the original.
Example of an Immutable Class
final class ImmutableStudent {
private final String name;
private final int age;
private final List<String> subjects;
// Constructor
public ImmutableStudent(String name, int age, List<String> subjects) {
this.name = name;
this.age = age;
// Defensive copy to prevent external modification
this.subjects = new ArrayList<>(subjects);
}
// Getter methods (No setters)
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getSubjects() {
// Returning defensive copy to prevent modification
return new ArrayList<>(subjects);
}
}
Benefits of Immutable Classes
- Thread Safety – No synchronization needed since the state doesn’t change.
- Reliable Hashing – Perfect for use in
collections like
HashMap
andHashSet
. - Security – Prevents accidental or malicious modifications.
- Simplified Code No need to handle state changes.
final class Student {
private final String name;
private final Address address; // Contains an immutable deep copy.
// Constructor with Deep Copy for Address
public Student(String name, Address address) {
this.name = name;
this.address = new Address(address.getCity()); // Deep Copy for immutability
}
// Getter methods (No Setters)
public String getName() {
return name;
}
public Address getAddress() {
return new Address(address.getCity()); // Defensive Copy
}
@Override
public String toString() {
return "Student{name='" + name + "', address=" + address.getCity() + "}";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Student)) return false;
Student other = (Student) obj;
return name.equals(other.name) && address.getCity().equals(other.address.getCity());
}
@Override
public int hashCode() {
return name.hashCode() + address.getCity().hashCode();
}
}
final class Address {
private final String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
@Override
public String toString() {
return "Address{city='" + city + "'}";
}
}
In Java, deep copy and shallow copy care two ways of copying objects, and they differ in how they handle references to nested or contained objects.