5 ways to ensure Immutability in Java
Immutability is very much desirable in programming. It allows for predictable, consistent, and safe behaviour of objects, making it easier to reason about the code, improving performance, and ensuring that the code is more robust and less error-prone. Based on my experience, here are 5 ways you can incorporate immutability in your code base —
1. Final fields
One of the simplest ways to achieve immutability in Java is to use final fields. A final field can only be assigned a value once, either in the constructor or during the object’s initialization. Here is an example:
public final class Person {
private final String firstName;
private final String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
2. Immutables library
One of the most popular libraries for creating immutable objects in Java is Google Guava’s Immutables library. It allows you to create immutable objects in a simple and consistent way, and it also supports more advanced features like custom validation and collections. Here is an example:
@Value.Immutable
public interface Person {
String firstName();
String lastName();
}
3. Type-level annotations
Use @NonNull, @Nullable or other type-level annotations. To prevent null values in the fields of immutable objects and to make it explicit, it is a good practice to use type-level annotations like @NonNull, @Nullable or others to specify the nullability of the fields. This can help catch mistakes early on and make the code more readable. Here is an example:
public final class Person {
private final @NonNull String firstName;
private final @NonNull String lastName;
public Person(@NonNull String firstName, @NonNull String lastName) {
this.firstName = firstName;
this.lastName
}
}
4. Immutable Builder Pattern
Using a builder class to create immutable objects is a powerful technique that allows you to create complex immutable objects with a simple, consistent interface. The builder class can be used to set values for the properties of the object, and then a build method is called to create the immutable object with the set properties. This allows you to create immutable objects with many optional fields, and ensures that the objects are created in a consistent way.
@Value.Immutable
public interface Person {
String firstName();
String lastName();
int age();
String address();
@Value.Builder
interface Builder {
Builder firstName(String firstName);
Builder lastName(String lastName);
Builder age(int age);
Builder address(String address);
Person build();
}
}
5. Value objects
One of the best ways to ensure immutability is to use value objects. Value objects are small, immutable objects that represent a single value, such as an email address or a date. They are typically used to encapsulate validation logic, and they can be used instead of primitives to make it clear what kind of data a variable represents. Here is an example:
public final class Email {
private final String email;
public Email(String email) {
if (!email.matches("^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$")) {
throw new IllegalArgumentException("Invalid email address");
}
this.email = email;
}
public String getEmail() {
return email;
}
}
If you want to connect with me, feel free to reach out on LinkedIn!
To take your Java development skills to the next level, be sure to checkout and follow Effective Java!