Immutability in Java

Vlad Ungureanu
The Startup
Published in
5 min readNov 19, 2020

An immutable class is a class that once initialized with a value, cannot change its state. This means that if we have a reference to an instance of an immutable object in our code, any changes done to that instance would result in a new instance being created and the reference changing to point to the new memory location.

Immutability In Java — Learn Stuff

An immutable class needs to comply to the following set of rules:

The class needs to be final so that it cannot be extended. Child classes can then be used for instantiation, risking the unwanted use of a non-immutable implementation.

All property fields within the class need to be private, adhering to the principle of encapsulation.

In order to correctly create an immutable class instance, it is most likely that there should be parameterized constructors which allow for the initial setting of the class state.

In order to exclude the possibility of changing class properties, after instantiation, the class cannot contain setter methods.

Making deep copies of your collection type fields ensures we cannot alter the state of the collection type fields.

Immutability in Action

First let’s take a look at this that at first seems like an immutable class:

import java.util.Map;public final class MutableClass {  private String field;
private Map<String, String> fieldMap;
public MutableClass(String field, Map<String, String> fieldMap) {
this.field = field;
this.fieldMap = fieldMap;
}
public String getField() {
return field;
}
public Map<String, String> getFieldMap() {
return fieldMap;
}
}

Now that we implemented our class, let’s see it in action.

import java.util.HashMap;
import java.util.Map;
public class App {public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("key", "value");

// Initializing our new "immutable" class
MutableClass mutable = new MutableClass("this is not immutable", map);
//we can easily add new elements to the map == changing the state
mutable.getFieldMap().put("unwanted key", "another value");
mutable.getFieldMap().keySet().forEach(e -> System.out.println(e));
}}
//console output
unwanted key
key

Clearly, we did not intend to be able to add elements to this collection of the class. This would mean that we change it’s state and this is not a characteristic of immutability.

import java.util.HashMap;
import java.util.Map;
public class AlmostMutableClass { private String field;
private Map<String, String> fieldMap;
public AlmostMutableClass(String field, Map<String, String> fieldMap) {
this.field = field;
this.fieldMap = fieldMap;
}
public String getField() {
return field;
}
public Map<String, String> getFieldMap() {
Map<String, String> deepCopy = new HashMap<String, String>();
for(String key : fieldMap.keySet()) {
deepCopy.put(key, fieldMap.get(key));
}
return deepCopy;
}
}

What we changed here was the get method, which now returns a deep copy of the collection that is referenced in the AlmostImmutableClass, which means that if we get the Map by calling the get method, even if we add to it, the map from our class would not change, but the changes would affect only the map we retrieved. However, if we still have access to the initial map, that was passed as parameter to the constructor, things are not that great. We can modify it, and by doing so, we would modify the state of the almost mutable class.

import java.util.HashMap;
import java.util.Map;
public class App {public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("good key", "value");

// initializing our new "immutable" class
AlmostMutableClass almostMutable = new AlmostMutableClass("this is not immutable", map);

//we cannot change the state
//if we try to add to the map we got from the object
System.out.println("Result after modifying the map after we get it from the object");
almostMutable.getFieldMap().put("bad key", "another value");
almostMutable.getFieldMap().keySet().forEach(e -> System.out.println(e));

System.out.println("Result of the object's map after modifying the initial map");
map.put("bad key", "another value");
almostMutable.getFieldMap().keySet().forEach(e -> System.out.println(e));

}
}//console output
Result after modifying the map after we get it from the object
good key
Result of the object's map after modifying the initial map
good key
bad key

There is one more thing we forgot to add. When setting the map through the constructor, we need to do the same thing as we did for the get method, so the constructor would in fact need to look like this:

public AlmostMutableClass(String field, Map<String, String> fieldMap) {
this.field = field;
Map<String, String> deepCopy = new HashMap<String, String>();
for(String key : fieldMap.keySet()) {
deepCopy.put(key, fieldMap.get(key));
}
this.fieldMap = deepCopy;
}
//console output
Result after modifying the map after we get it from the object
good key
Result of the object's map after modifying the initial map
good key

While there are benefits when using immutable objects this is not always the best practice for our code. Usually, when we are implementing something, we need to create objects and sometimes also alter their state, to reflect the changes the system is going through. Simply put, we need to modify our data, and it seems counter-intuitive to create new objects every time some changes occur as this would have a great impact on memory, and since we need our application to perform within optimal parameters, we need to use the system’s resources with care.

String Immutability in Java

The String class is probably the most widely used class in the Java programming language and it represents a sequential collection of characters. The purpose of this class is to facilitate the use of character sequences in our code, by providing various methods used to interact with the character sequence. For example, the String class provides methods for extracting characters, splitting the sequence, finding and replacing sub-sequences and many others. Like all the other wrapper classes in Java (Integer, Boolean and so on), the String class is immutable. String immutability provides a series of advantages:

String are thread safe

Strings are specifically managed by the JVM through the use of a dedicated memory space, called “String constant pool”. This means that if two different String variables have the same value, they will point to the same memory location.

The String class is a great candidate for keys in key based collections since they cannot by modified by mistake.

Hash code implementation for the String class is optimized for caching leading to an increase in performance for hash-based collections using Strings.

Sensitive String values, like usernames and passwords, cannot be mistakenly changed during execution even if we pass references to different methods.

For more information and 100+ free courses visit:

http://learnstuff.io

For online courses and webinars visit:

https://learnstuffacademy.io/

--

--

Vlad Ungureanu
The Startup

Software Developer, Trainer, Personal Development Enthusiast.