What is immutable object in Java?

Altun Aliyev
4 min readMar 24, 2023

--

In this article, I will explain to you what an immutable object is.

Java is an object-oriented programming language that supports the creation of immutable objects. An immutable object is an object whose state cannot be changed after it has been created. In other words, once an immutable object is created, its state remains the same throughout its lifetime.

Immutable objects have several benefits. They are inherently thread-safe, as they can be safely shared between threads without the risk of concurrent modification. They also provide a simple and clear API, as there are no complex state transitions to consider. Finally, they can improve performance by avoiding the need for defensive copying.

Let’s take a look at an example of how to create an immutable object in Java. We’ll start with a simple class that represents a book:

public class Book {
private final String title;
private final String author;
private final int year;

public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}

public String getTitle() {
return title;
}

public String getAuthor() {
return author;
}

public int getYear() {
return year;
}
}

In this example, we’ve marked the title, author, and year fields as final. This means that once they have been assigned a value, they cannot be changed. We've also omitted any setters, so there is no way to modify the state of a Book object once it has been created.

To create a new Book object, we simply call the constructor with the desired values:

Book book = new Book("The Catcher in the Rye", "J.D. Salinger", 1951);

Once this object has been created, we can be sure that its state will not change. For example, if we try to modify the title field directly, we'll get a compile-time error:

book.title = "New Title"; // Compile-time error: cannot assign a value to final variable 'title'

This is because the title field is marked as final, so it cannot be modified once it has been assigned a value.

Now, let’s consider a more complex example, where we have an object that contains a mutable field:

public class Address {
private String street;
private String city;
private String state;
private String zip;

public Address(String street, String city, String state, String zip) {
this.street = street;
this.city = city;
this.state = state;
this.zip = zip;
}

public String getStreet() {
return street;
}

public String getCity() {
return city;
}

public String getState() {
return state;
}

public String getZip() {
return zip;
}
}

In this example, the Address class has four fields: street, city, state, and zip. These fields are not marked as final, so they could be modified after the object is created. To make this object immutable, we need to ensure that the fields are not modified.

One way to achieve this is to make a defensive copy of the mutable field in the constructor. For example:

public class ImmutablePerson {
private final String name;
private final Address address;

public ImmutablePerson(String name, Address address) {
this.name = name;
this.address = new Address(
address.getStreet(),
address.getCity(),
address.getState(),
address.getZip()
);
}

public String getName() {
return name;
}

public Address getAddress() {
return new Address(
address.getStreet(),
address.getCity(),
address.getState(),
address.getZip()
);
}

In this example, we’ve created an ImmutablePerson class that has two fields: name and address. The name field is a String, which is immutable by default. The address field is an instance of the Address class, which contains mutable fields.

To make the ImmutablePerson class truly immutable, we've made a defensive copy of the Address object in the constructor. This ensures that the original Address object cannot be modified externally. Additionally, the getAddress method returns a defensive copy of the Address object, so clients of the class cannot modify the internal state of the object.

Here’s an example of how to create a new ImmutablePerson object:

Address address = new Address("123 Main St", "Anytown", "CA", "12345");
ImmutablePerson person = new ImmutablePerson("John Smith", address);

Once the person object has been created, its state cannot be modified. For example, if we try to modify the address field directly, we'll get a compile-time error:

person.address = new Address("456 Elm St", "Othertown", "CA", "67890"); // Compile-time error: cannot assign a value to final variable 'address'

This is because the address field is marked as final, so it cannot be modified once it has been assigned a value.

However, it’s important to note that this approach does have some drawbacks. Creating defensive copies of objects can be expensive in terms of performance and memory usage, especially for large objects. Additionally, if the Address object contains references to other mutable objects, we would need to create defensive copies of those objects as well, which could become very complex.

Finally, I would like to add that, In Java, there are several built-in classes that are immutable. Here are a few examples:

  1. String: The String class in Java is immutable. Once a String object is created, its value cannot be changed. Any operation that appears to modify a String object actually creates a new String object with the modified value.
  2. Integer, Double, Boolean, etc.: The wrapper classes for primitive types (Integer, Double, Boolean, etc.) are also immutable. Once an object of one of these classes is created, its value cannot be changed.
  3. java.time classes: The java.time package in Java 8 introduced a set of new date and time classes that are also immutable. For example, the LocalDate class represents a date (year, month, and day) and cannot be modified once created.
  4. java.math classes: The java.math package contains a set of classes for performing arbitrary-precision arithmetic. The BigInteger and BigDecimal classes are both immutable.

Overall, immutability can be a powerful tool for creating reliable and thread-safe code in Java. By ensuring that objects cannot be modified after they are created, we can avoid many common pitfalls and make our code easier to reason about.

--

--