To create an immutable class in Java, follow these steps and principles:
1. Make the class `final`:
Declare your class with the `final` keyword. This prevents the class from being extended (subclassed), which is necessary to ensure immutability.
```java
public final class ImmutableClass {
// ...
}
```
2. Declare all fields as `private` and `final`:
All fields of the immutable class should be declared as `private` and `final`. This restricts direct access and modification to the fields.
```java
public final class ImmutableClass {
private final int intValue;
private final String stringValue;
// ...
}
```
3. Provide a constructor to initialize all fields:
Create a constructor that accepts parameters for initializing all fields of the class. Assign these parameters to the `final` fields in the constructor.
```java
public ImmutableClass(int intValue, String stringValue) {
this.intValue = intValue;
this.stringValue = stringValue;
}
```
4. Avoid exposing mutable objects:
If your class contains mutable objects (e.g., collections or arrays), make sure to return a copy of them or provide access methods that return copies, not the actual objects. This prevents clients from modifying the internal state.
```java
public List<String> getStringList() {
// Return a copy of the internal list to maintain immutability.
return new ArrayList<>(stringList);
}
```
5. Don't provide setters:
Remove any setter methods from the class, as immutability means objects cannot be modified after creation.
6. Avoid modifying internal state:
Ensure that none of the methods within the class modify the state of the object. All methods should be side-effect-free.
7. Provide accessors for all fields:
Create accessor methods (getters) for accessing the values of the `final` fields.
```java
public int getIntValue() {
return intValue;
}
public String getStringValue() {
return stringValue;
}
```
8. Implement `equals()` and `hashCode()` methods:
Override the `equals()` and `hashCode()` methods to ensure proper comparison and hashing of objects based on their values.
9. Make the class Serializable (optional):
If your class needs to be serialized, implement the `Serializable` interface. Ensure that the fields are also `Serializable` or marked as `transient` if needed.
10. Document the immutability:
Clearly document that your class is immutable in the class's Javadoc comments. This helps other developers understand and respect the immutability contract.
11. Consider creating a builder (optional):
If your class has many fields, you may consider creating a builder class to simplify the object creation process.
12. Thread safety:
Immutability inherently provides a degree of thread safety. However, ensure that there are no race conditions in methods, and synchronize if necessary.
Here's a complete example of an immutable class:
```java
public final class ImmutableClass {
private final int intValue;
private final String stringValue;
private final List<String> stringList;
public ImmutableClass(int intValue, String stringValue, List<String> stringList) {
this.intValue = intValue;
this.stringValue = stringValue;
this.stringList = new ArrayList<>(stringList); // Make a defensive copy
}
public int getIntValue() {
return intValue;
}
public String getStringValue() {
return stringValue;
}
public List<String> getStringList() {
return new ArrayList<>(stringList); // Return a copy to maintain immutability
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImmutableClass that = (ImmutableClass) o;
return intValue == that.intValue && Objects.equals(stringValue, that.stringValue);
}
@Override
public int hashCode() {
return Objects.hash(intValue, stringValue);
}
}
``