Immutability in Java

Hello, Habr. In anticipation of the imminent start of the course Preparing for Oracle Java Programmer Certification (OCAJP) prepared a traditional translation of the material for you.

We also invite everyone to participate in the open demo lesson “Constructors and Initialization Blocks”. In this free webinar, we:
– Let’s analyze the constructor for parts
– Define the finalists (final variables)
– Putting things in order (initialization)


An immutable class is a class that, after initialization, cannot change its state. That is, if the code contains a reference to an instance of an immutable class, then any changes in it lead to the creation of a new instance.

For a class to be immutable, it must meet the following requirements:

  • Must be declared final so that it cannot be inherited from. Otherwise, child classes may break immutability.

  • All fields of the class must be private in accordance with the principles of encapsulation.

  • To create an instance correctly, it must have parameterized constructors through which the initial initialization of the class fields is carried out.

  • To exclude the possibility of changing the state after instantiation, the class should not have setters.

  • For collection fields, you need to make deep copies to ensure that they are immutable.

Immutability in action

Let’s start with the following class, which looks immutable at first glance:

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 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");
    
  // Инициализация нашего "иммутабельного" класса
  MutableClass mutable = new MutableClass("this is not immutable", map);
  // Можно легко добавлять элементы в map == изменение состояния
  mutable.getFieldMap().put("unwanted key", "another value");
  mutable.getFieldMap().keySet().forEach(e ->  System.out.println(e));
}
}
// Вывод в консоли
unwanted key
key

Obviously, we want to prohibit adding items to the collection, since this is a change in the state of the object, that is, the lack 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;
}
}

Here we have changed the method getFieldMapwhich now returns a deep copy of the collection referenced in AlmostMutableClass… It turns out that if we get Mapby calling the method getFieldMap, and add an element to it, then on map from our class it will not be affected in any way. Will only change mapwe received.

However, if we still have access to the original map, which was passed as a parameter to the constructor, then things are not so good. We can change it, thereby changing the state of the object.

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");
    
  // Инициализация нашего "иммутабельного" класса
  AlmostMutableClass almostMutable = new AlmostMutableClass("this is not immutable", map);
  
  // Мы не можем изменять состояние объекта 
  // через добавление элементов в полученную map
  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));
    
  }
}
// Вывод в консоли
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

We forgot to do the same in the constructor as in the method getFieldMap… As a result, the constructor should 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;
}
// Вывод в консоли
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

Although there are advantages to using immutable objects, their use is not always justified. Usually, we need to both create objects and modify them to reflect the changes taking place in the system.

That is, we need to change the data, and it is illogical to create new objects with each change, since this increases the used memory, and we want to develop efficient applications and make optimal use of system resources.

String Immutability in Java Baeldung

Class Stringrepresenting a character set is probably the most popular class in Java. Its purpose is to simplify working with strings by providing various methods for handling them.

For example, in the class String there are methods for getting characters, highlighting substrings, searching, replacing, and many others. Like other wrapper classes in Java (Integer, Boolean, etc.), the class String is immutable.

String immutability provides the following benefits:

  • Strings are thread safe.

  • For strings, you can use a special area of ​​memory called a “string pool”. Due to which two different variables of type String with the same value will point to the same memory area.

  • Strings are a great candidate for keys in collections because they cannot be changed by mistake.

  • The String class caches the hash code, which improves the performance of hash collections using String

  • Sensitive data, such as usernames and passwords, cannot be changed by mistake at runtime, even if references to them are passed between different methods.


More about the course Preparing for Oracle Java Programmer Certification (OCAJP)

Watch the webinar “Constructors and Initialization Blocks”.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *