How to implement an immutable class like String in Java?

If you ask what is the Java class that you use the most in daily development, Ah Fen will bet that it is definitely String.class. Speaking of String, everyone knows that String is an immutable class; although it is used a lot, have you ever thought about how to create your own immutable class? In this article, Ah Fen will take you to practice and create your own immutable class.

Characteristics
Before manually writing code, let's first understand what characteristics immutable classes have.

When defining a class, you need to use the final keyword for modification: the reason for using final for modification is because it can avoid being inherited by other classes. Once there is subclass inheritance, it will destroy the immutability mechanism of the parent class; member variables need to use the final
key Word modification, and needs to be private: prevent attributes from being modified externally;
member variables cannot provide setter methods, only getter methods: avoid external modification, and avoid returning member variables themselves;
provide constructors for all fields; know
in practice
After understanding some basic characteristics of immutable classes, let's actually write code to operate, and we will verify what kind of problems will occur if we do not write according to the above requirements.

Here we define a Teacher class to test it. According to the points we mentioned above, we add final code to the class and property definitions as shown below.

package com.example.demo.immutable;

import java.util.List;
import java.util.Map;

public final class Teacher {
    
    
  private final String name;
  private final List<String> students;
  private final Address address;
  private final Map<String, String> metadata;

  public Teacher(String name, List<String> students, Address address, Map<String, String> metadata) {
    
    
    this.name = name;
    this.students = students;
    this.address = address;
    this.metadata = metadata;
  }

  public String getName() {
    
    
    return name;
  }


  public List<String> getStudents() {
    
    
    return students;
  }

  public Address getAddress() {
    
    
    return address;
  }

  public Map<String, String> getMetadata() {
    
    
    return metadata;
  }
}
package com.example.demo.immutable;

public class Address {
    
    
  private String country;
  private String city;

  public String getCountry() {
    
    
    return country;
  }

  public void setCountry(String country) {
    
    
    this.country = country;
  }

  public String getCity() {
    
    
    return city;
  }

  public void setCity(String city) {
    
    
    this.city = city;
  }
}

Let's think about whether the above code is really immutable. Okay, let's think for three seconds and count to three silently. To answer this question, let's look at the test code below.

The result of the operation is shown in the screenshot below. Through testing, we can find that simply adding the final keyword cannot solve the immutability. Our current teacher instance has been modified by the outer layer to remove the member variables.

In order to solve this problem, we also need to modify our Teacher class. The first thing we can think of is that the two member variables of students and metadata cannot be directly returned to the outer layer, otherwise the modification of the outer layer will directly affect our inability to Change the class, then we can modify the getter method, copy the member variable and return it instead of returning it directly, modify the code as follows

  public List<String> getStudents() {
    
    
    return new ArrayList<>(students);
    //return students;
  }
    public Map<String, String> getMetadata() {
    
    
    return new HashMap<>(metadata);
  //return metadata;
  }

We run the above test code again, and we can see that the returned data is as follows. This time, our students and metadata member variables have not been modified by the outer layer. But there is still a problem with our address member variable, it doesn't matter, let's move on.
insert image description here

Naturally, in order to solve the problem of address, we thought of making a copy, and returning a copy object when calling the getter method instead of directly returning the member variable. Then we need to transform the Address class and turn it into Cloneable. We implement the interface and then override a clone method. The code is as follows

package com.example.demo.immutable;

public class Address implements Cloneable{
    
    
  ...// 省略
  @Override
  public Address clone() {
    
    
    try {
    
    
      return (Address) super.clone();
    } catch (CloneNotSupportedException e) {
    
    
      throw new AssertionError();
    }
  }
}

Then modify the getAddress method of Teacher

  public Address getAddress() {
    
    
  //return address;
    return address.clone();
  }

Next, let's run the test code again, the result is as follows, we can see that the member variables of our teacher instance have not been modified this time, so far we have completed the creation of an immutable object!
insert image description here

In the implementation of String,
we looked at the operation of custom implementation of immutable classes. Next, we briefly look at how the String class is immutable. Through the source code, we can see that String also uses the keyword final to avoid being inherited by subclasses. , and the corresponding member variables that store specific values ​​also use the final keyword.
insert image description here

And the method substring provided externally is also a new String object provided externally in the form of copying.
insert image description here
insert image description here

Original link: https://mp.weixin.qq.com/s/fOXeblVcw9Ph5ug5eehb-A

Guess you like

Origin blog.csdn.net/weixin_44834205/article/details/128069520