Java Recruitment Questions - Immutable Objects


04 Feb 2020  Michal Fabjanski  7 mins read.

Java Recruitment Questions - Immutable Objects

In Java, object variability is something very common. Until recently, it was normal for most beans to have getters and setters (In many projects this trend is still continuing). Increasingly, at interviews, interviewers ask: “What are immutable objects and what are their advantages and disadvantages?”

What is an immutable object?

An immutable object is one which, after being created and initialized, remains unchanged and, importantly, cannot be changed (in a conventional way, of course). This means that such an object does not provide methods that allow changing its state. And all its fields are private (or public final).

This changeability is a little apparent, because, using reflection, you can change the field values in each class. However, this does not mean that for this reason, we should give up immutable objects.

How to ensure the object’s immutability?

Apart from the fact that all fields should be private, they should be final. And the class itself should be marked as final so that it cannot be inherited. Such an object should not provide methods to modify its internal state, e.g. setters.

But private, final fields are not everything. It is important what types of data we store in these fields. It should also be unchangeable! If the fields in your object are primitives, primitive wrappers or Strings then there is no problem. All these types are immutable. But if you use your own objects, you must ensure that they are unchangeable.

If you use a collection, you must also ensure that it remains immutable. There are several possibilities:

  • You can use special methods from the Collections class that return an appropriate unmodified view of the collection:
    • static <T> List<T> unmodifiableList(List<? extends T> list)
    • static <T> Set<T> unmodifiableSet(Set<? extends T> s)
    • static <K,V> Map<K,V> unmodifiableMap(Map<? extends K,? extends V> m) Each of these methods takes a list, a set or a map as an argument and returns the list, set or map with the same content as the argument, but different from the original in that the attempt to change them, for example add or remove, causes an exception UnsupportedOperationException
  • Use the immutable collections. If you are using Java 8 or above, you must use a library that will provide you with an implementation of the unchanging collections (e.g. Guava). If you are using newer versions of Java (9+), you have a collection that is immutable in the standard packages, e.g. List.of("a", "b", "c");. In Java 10, there are also copyOf(...) methods that allow you to copy the standard collection and convert it to the fixed one.

If you use a primitive array - you have a little problem because the arrays are inherently changeable. And using them in immutable objects is pointless. Of course, when creating an object, we can copy all arrays, but this does not change the fact that the copied arrays are also changeable. In this case, it is best to use the collection.

How to create immutable objects?

Such objects can be created in three ways. Through the constructor and that is the easiest way. But it has a fundamental disadvantage. The more fields to initiate, the more parameters in the constructor. That’s why you shouldn’t create objects that have more than 2-3 fields.

          
public final class Animal {

    private final String name;
    private final String ownerName;

    public Animal(String name, String ownerName) {
        this.name = name;
        this.ownerName = ownerName;
    }

    public String getName() {
        return name;
    }

    public String getOwnerName() {
        return ownerName;
    }
}

// Creation
Animal animal = new Animal("Tina", "John");

Another way is the factory method. As with the constructor, the more fields, the more parameters. But this approach has such an advantage that we can create several such methods with a different names, with different set of parameters, which improves readability.

A third way to create immutable objects is to use the builder pattern. In order to use the builder, it must be implemented inside the class so that it has access to private class fields.

          
public final class User {
    private int age;
    private String firstName;
    private String lastName;

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private User user = new User();

        public Builder firstName(String firstName) {
            user.firstName = firstName;
            return this;
        }

        public Builder lastName(String lastName) {
            user.lastName = lastName;
            return this;
        }

        public Builder age(int age) {
            user.age = age;
            return this;
        }

        public User build(){
            return user;
        }
    }

// Creation
 User user = User.builder()
                .firstName("John")
                .lastName("People")
                .age(45)
                .build();

We can write such a builder manually or use some kind of IDE plugin to generate it for us. Another option is to use Lombok and @Builder annotation.

Advantages of immutable objects

These objects make us avoid accidental changes, often in very inappropriate places. If an object is changeable, there will definitely be someone who wants to change it where it should not.

A good example of such a situation is the object that we pass as a parameter of the method. Such an object can be passed between multiple application layers, between multiple method calls. It can be passed on very deep in the call hierarchy. This makes it very difficult to identify where it was changed. This can lead to many strange and difficult to solve problems.

Using immutable objects we do not have such problems and the design of our application improves.

Invariant objects are also safe for multithreaded use. If an object is not changed, it can be safely transferred between threads without synchronization.

Another advantage is that such objects are ideal as key objects in maps. In a situation where keys are variable, after changing the key object its hashcode changes, making it impossible to find the stored value in HashMap.

Disadvantages of immutable objects

Writing this post I didn’t think about the disadvantages of creating immutable classes. Fortunately, a user potracheno informed about it in the comment. Thanks!

Immutable objects have far more advantages than disadvantages. The only thing that comes to my mind is the cost of memory. With immutability, any time you need to modify data, you need to create a new object. This can be expensive. If you are sure that you will not need immutability (e.g. you are creating a simple single-threaded application) - do not create immutable code.

When to use immutable objects?

You can use the same objects anywhere and anytime. You don’t have to use multithreading to use immutable objects. Applications that work in one thread also gain a lot when using immutable objects.

Summary

Immutable objects are a very important element of application building, which makes applications more legible and consistent, as well as more error-proof. Every developer should know how to use immutable objects and what benefits are associated with their use.