[Java] What is an immutable object? [How to make]

7 minute read

I’ve heard that immutable objects are safe, or make immutable objects as much as possible, but what is that? I think there are many people

Here I will explain what immutable objects are and how to make them Immutable objects can be created regardless of language, but I think I’ll explain it in Java.

This is an explanation for beginners, but if you want to read a more detailed explanation, please buy the book below. Effective Java 3rd Edition

What is an immutable object

To put it simply, it means an object whose data does not change.

I don’t think it makes sense, so I’ll explain it using Java’s String and StringBuilder. First look at the code below


String str = "A";

for(int i = 0; i <2 ;i++){
  str += "A";
}

System.out.println(str);

The output result is as follows


AAA

This code simply adds A to str twice In the above code, str is declared as String type, but even if you declare str as StringBuilder type, the output result does not change

However, the method of instantiation is different between String and StringBuilder Let’s see what kind of instance will remain in the end

For StringBuilder StringBuilder.png Since only the same instance as the output result is simply generated, only one instance of AAA will remain in the end

For String String.png In case of String, another instance is created each time A is added So A, AA, AAA instances will remain Instances of A and AA are out of reference and are subject to GC

Difference between ###String and StringBuilder As you can see from the above example, while StringBuilder has the value of one instance rewritten, String has another instance without rewriting the value. Generating </font color>

StringBuilder only instantiates once, so it’s cheaper String is instantiated many times, so the load is high

Seeing this, String doesn’t make sense! You may feel that it is wrong

Immutable and mutable objects

StringBuilder is called mutable object because its value is rewritten Strings are called immutable objects because they haven’t been rewritten

As mentioned above, the advantage of StringBuilder leads to performance improvement. In this example, A is combined only twice, but when it reaches 10000 times, it becomes impossible to make a fool of the generation cost of the instance.

However, even if it is the same instance, the value will be rewritten depending on the timing, such as A, AA, AAAAAAAA, etc. This is a big problem, and making a single change will affect any objects that reference that object. Variable Object.png

That is, **Using mutable objects has some side-effects every time a change is made**</font color>

What if it’s a String It costs money to create multiple instances, but one object always keeps the same value If you want an instance with a different value, create another instance So it doesn’t affect other objects Immutable object.png In other words, ** you can use an immutable object to ensure that its value does not change **</font color>

Immutable objects are also useful for multithreaded programming The value of the immutable object doesn’t change, so you don’t have to synchronize it. Mutable objects are a problem in a multithreaded environment

** Immutable objects are inherently thread safe **</font color> Even if you make a mutable object, it will help prevent accidents by reducing the mutable parts as much as possible. # How to make an immutable object Now that you know how useful immutable objects are, From here, I'll explain how to actually create an immutable object ### Prevent inheritance of classes Allowing a class to inherit will break encapsulation, such as inheriting and adding strange methods. Declare a class so that it can't be inherited, because we don't want to have a problem that it is an immutable object or a mutable object depending on how it is created even though the types are the same. ```java final class TestClass{} ``` Since it is not visible from the outside if it is a class of the encapsulated module, it is practically impossible to inherit without adding final, but if it is not intended to be inherited, add final to inherit it. It's common to ban (this is also true for mutable objects) ### Make all fields constant Immutable objects don't want their values changed, so make them constant Also, set visibility so that variables are not directly referenced ```java private final int num; private final StringBuilder str; ``` There is no problem if you declare the same regardless of the basic data type and the reference data type. In case of reference data type, it is necessary to change the handling a little, but it will be described later. ### don't make a mutator A mutator is an operation that rewrites values such as a setter. Of course we don't need a method to do that, because we don't want to rewrite the value ### Watch out for getters First look at the code below ```java public int getNum(){ return num; } public StringBuilder getStr(){ return str; } ``` The return value of getNum method is the basic data type, and the return value of getStr method is the reference data type. In case of basic data type, a copy of the value of num is returned, so there is no particular problem However, in case of reference data type, the problem will occur because the reference is returned as it is. Since the instance reference can be obtained by the side that called the getStr method, the value of the str instance content can be rewritten. In order to prevent it, I tried improving the code above ```java public String getStr(){ String result = str.toString; return result; } ``` The StringBuilder class had a method called toString which turns mutable objects into immutable objects so I tried using them However, the part of this type conversion is not so important in the explanation of "Beware of getters". The important thing is that copying the value back</font color> Create another copy instance and return the copy instance reference By doing this, you can tamper with the copy instance by whatever means you call the getStr method, but you won't be able to interfere with the original instance. This technique is called **defensive copy** By using defensive copy, even classes that handle mutable objects such as StringBuilder can be made immutable Here are some things to keep in mind when using defensive copy ① Performance may be degraded In the case of arrays and Lists, the contents may have to be copied, so performance may deteriorate. But remember that immutable objects are worth the price ② Do not use the clone method Please do not use it when making a defensive copy, because the clone method of java has some problems. ##### Sample code I will put the sample code of the immutable object explained so far (The constructor part is added) ```java final class TestClass{ private final int num; private final StringBuilder str; public TestClass(int num,StringBuilder str){ this.num = num; // Make a defensive copy here too this.str = new StringBuilder(str); } public int getNum(){ return num; } public String getStr(){ String result = str.toString; return result; } } ``` *Added July 1, 2020 (Thank you for pointing out @saka1029) You also have to do a defensive copy when you take a mutable object as an argument in the constructor If you don't do this, you'll end up with the same reference in the caller and TestClass, and you'll be able to rewrite the value**Use defensive copy when receiving and passing mutable objects**</font color> ### bonus The sample code above makes the constructor public, but Effective Java recommends a static factory This also applies to current API development Let's rewrite the sample code above a little ```java final class TestClass{ private final int num; private final StringBuilder str; // prohibits the use of constructors from the outside // The caller cannot use the following code // TestClass tc = new TestClass(10,"AAA"); private TestClass(int num,StringBuilder str){ this.num = num; this.str = new StringBuilder(str); } //static factory public static final TestClass newInstance(int num,StringBuilder str){ return new TestClass(num,str); } public int getNum(){ return num; } public String getStr(){ String result = str.toString; return result; } } ``` The calling code looks like this ```java TestClass tc = TestClass.newInstance(10,"AAA"); ``` In the case of the above code, a new TestClass type instance is generated each time the staticFactory newInstance method is called, so it is not a singleton But if you don't want to be a singleton you can use this technique If you want to make a singleton, do not use new in static factory method, just create an instance in advance and return With the advent of DI containers, the chances of calling a static factory directly may have decreased, but this technique is still active. # Summary I introduced what immutable objects are and how to create them. It is no exaggeration to say that there is no system that does not require immutable objects in any system, so please remember and use it.