[JAVA] What is an immutable object? [Explanation of how to make]

I've heard that immutable objects are safe, or try to create immutable objects as much as possible, but what is that? I think there are many people

Here I will explain what an immutable object is and how to create an immutable object. Immutable objects can be created regardless of language, but I think I'll explain it in Java.

The explanation is for beginners here, but if you want to read more detailed explanation, please buy the following books. Effective Java 3rd Edition

What is an immutable object?

To put it simply, it is an object that has the same data.

I don't know what it means, so I'll explain it using Java 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 a String type, but declaring str as a StringBuilder type does not change the output result.

However, the method of instantiation is different between String and StringBuilder. Let's see what kind of instance will be left in the end

** For StringBuilder ** StringBuilder.png Since only the same instance as the output result is generated, the final remaining instance is only one AAA.

** For String ** String.png In the case of String, another instance is created each time A is added. Therefore, A, AA, AAA instances 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, StringBuilder has the value of one instance rewritten, while String has another instance without rewriting the value. Generating </ font> color </ font>

StringBuilder only instantiates once, so it's less expensive Since String has created instances many times, the load will be higher accordingly.

Looking at this, there is no point in using String! You may feel, but ** it's a mistake **

Immutable and variable objects

StringBuilder is called a mutable object because it rewrites the value String is called an immutable object because it hasn't rewritten its value

As mentioned above, the advantages of StringBuilder lead to improved performance. In this example, A is combined only twice, but if this is 10,000 times, it will not be possible to make a fool of the instance creation cost.

However, even though it is the same instance, the value is rewritten depending on the timing, such as A, AA, AAAAAAA. This is a big problem, and once you make a change, it also affects the objects that reference it. 可変オブジェクト.png

This means that ** variable objects will have 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 Therefore, it does not affect other objects. 不変オブジェクト.png That is, ** Immutable objects can be used to ensure that their values do not change ** </ font color>

Immutable objects are also useful for multithreaded programming Immutable objects don't change in value, so you don't have to do things like synchronize with synchronized ... The problem with multithreaded environments is that they are variable objects.

** Immutable objects are inherently thread-safe ** </ font color>

Even if you create a variable object, reducing the variable part as much as possible will help prevent accidents.

How to make an immutable object

So far, you can see how useful immutable objects are. From here, I'll explain how to actually create an immutable object.

Prevent class inheritance

If you allow the class to be inherited, the encapsulation will be broken, such as being inherited and adding strange methods. I do not want to cause problems such as immutable object or variable object depending on how it is created even though the type is the same, so declare class so that it can not be inherited


final class TestClass{}

If it is a class of an encapsulated module, it is not visible from the outside, so it is practically impossible to inherit without adding final, but if you do not intend to inherit it, add final to inherit It's common to forbid (the same is true for variable objects)

Make all fields constant

Immutable objects don't want their values changed, so make them all constants It also sets visibility so that variables are not referenced directly.


private final int num;
private final StringBuilder str;

There is no problem if you declare in the same way regardless of the basic data type and reference data type. In the case of reference data type, it is necessary to change the treatment a little, but it will be described later.

Don't make a mutator

A mutator is an operation that rewrites values such as setters. I don't want to rewrite the value, so of course I don't need a method to do that

Watch out for getters

First look at the code below


public int getNum(){
  return num;
}

public StringBuilder getStr(){
  return str;
}

The return value of the getNum method is the basic data type, and the return value of the getStr method is the reference data type. For basic data types, a copy of the value of num is returned, so there is no problem. However, in the case of the reference data type, the problem occurs because the reference is returned as it is.

Since the instance reference can be obtained on the side that called the getStr method, the value inside the str instance can be rewritten.

To prevent that, I tried to improve the previous code


public String getStr(){
  String result = str.toString;
  return result;
}

The StringBuilder class had a method called the toString method that turns a variable object into an immutable object, so I tried using it. However, this type conversion part is not very important in the "Watch out for getters" description. The important thing is that is copying and returning the value </ font>

Create another copy instance and return a reference to the copy instance By doing this, the caller of the getStr method can use any means to tamper with the copy instance, but it will not interfere with the original instance.

This technique is called ** defensive copy ** By using defensive copy, even a class that handles variable objects like StringBuilder can make the class an immutable object.

There are some caveats when using defensive copy ① Performance may deteriorate In the case of arrays, Lists, etc., performance may be degraded because all the contents must be copied. But keep in mind that immutable objects are worth the price. ② Do not use the clone method The java clone method has some problems, so please do not use it when making a defensive copy

Sample code

I will put the sample code of the immutable object explained so far. (The constructor part has been added)


final class TestClass{

  private final int num;
  private final StringBuilder str;

  public TestClass(int num,StringBuilder str){
    this.num = num;
    //Also make a defensive copy here
    this.str = new StringBuilder(str);
  }

  public int getNum(){
    return num;
  }

  public String getStr(){
    String result = str.toString;
    return result;
  }

}
  • Added on July 1, 2020 (Thanks to @ saka1029 for pointing out) You also need to make a defensive copy when the constructor takes a mutable object as an argument If you don't do this, the caller and TestClass will have the same reference and you will be able to rewrite the value. ** Use a defensive copy when receiving and passing variable objects ** </ font color>

bonus

In the sample code above, the constructor is public, but Effective Java recommends a static factory. This also applies to current API development Let's rewrite the sample code a little earlier


final class TestClass{

  private final int num;
  private final StringBuilder str;

  //Prohibiting the use of constructors from the outside
  //The following code cannot be used on the caller
  // 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


TestClass tc = TestClass.newInstance(10,"AAA");

In the case of the above code, it will not be a singleton because a new instance of type TestClass will be created each time the newInstance method, which is a static factory, is called. But if you don't want to be a singleton you can use this technique

If you want to make it a singleton, do not use new in the static factory method, just create an instance in advance and return

The advent of DI containers may reduce the chances of calling static factor directly, but this technique is still active.

Summary

I introduced what an immutable object is and how to make it.

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.

Recommended Posts