SoFunction
Updated on 2025-03-08

Analysis of the principle of Java String immutability implementation

1. Principle

1. Invariant mode (immutable object)

Synchronous operations seem to be essential in parallel software development. When multiple threads read and write to the same object, it is necessary to synchronize the object in order to ensure the consistency and correctness of the object data. Synchronous operation is quite detrimental to system performance. In order to remove these synchronization operations as much as possible and improve the performance of parallel programs, an immutable object can be used. Relying on the invariance of the object, it can ensure that it always maintains the consistency and correctness of the internal state in a multi-threaded environment without synchronization operations. This is the unchanging mode.

The invariant pattern is inherently multi-thread friendly. Its core idea is that once an object is created, its internal state will never change. Therefore, no thread can modify its internal state and data, and its internal state will never change on its own. Based on these characteristics, there is no need for synchronous control of multi-threaded operations of invariant objects.

At the same time, it is also necessary to note that there is a certain difference between the invariant mode and the read-only attribute. The invariant mode has stronger consistency and invariance than the read attribute. For objects with read-only attributes, the object itself cannot be modified by other threads, but the object body state may be modified by itself. For example, the survival time of an object (the time difference between the object creation time and the current time) is read-only because no third-party thread can modify this attribute, but this is a variable attribute because as time goes by, the survival time changes at all times. The unchanged mode requires that no matter what the reason, the internal state and data of the object will remain absolutely stable after it is created.

2. How to implement immutable objects

In Java language, the implementation of the unchanged pattern is very simple. To ensure that the object is created without any changes and ensure that the unchanged mode works normally, you only need to pay attention to the following 4 points:

  • Removes setter methods and all methods to modify their own properties.
  • Set all attributes to private and tag them with final to ensure that they are not modified
  • Make sure there are no subclasses that can overload and modify its behavior.
  • There is a constructor that can create a complete object.

Does it match the final function? Let’s review the role of final in java.

  • Final modification class means that the class cannot be inherited, commonly known as the class of dying descendants, all methods of this class automatically become final methods
  • Final modification method means that the subclass cannot be rewritten.
  • Final modifys the basic data type variable, indicating that the variable is a constant and the value cannot be modified any more
  • Final modify the reference type variable, indicating that the reference cannot point to other objects after constructing the object, but the state of the object pointed to by the reference can be changed

It should be noted here: when using final to modify the basic type variable, the basic type variable cannot be reassigned, so the basic type variable cannot be changed. But for reference type variables, it only saves a reference. Final only ensures that the address referenced by this reference variable will not change, that is, the same object is always referenced, but this object can be completely changed. For example, a final reference pointing to an array must point to the array pointed to at the initialization time from then on, but the content of this array can be completely changed.

2. String source code analysis

The following is part of the source code of the String class in jdk1.8.

public final class String implements , Comparable<String>, CharSequence {  
    /** The value is used for character storage. */
  private final char value[];  /** Cache the hash code for the string */
    private int hash; // Default to 0
 
  /** use serialVersionUID from JDK 1.0.2 for interoperability */
  private static final long serialVersionUID = -6849794470754667710L;  /**
     ...}

First of all, we can see that the String class uses final modifier, indicating that the String class is not inheritable. Then, we mainly focus on the member variable value of the String class. The value is of type char[], so the String object is actually encapsulated with this character array.

Let’s look at the value modifier. It uses private and does not provide a setter method. Therefore, the value cannot be modified outside the String class. At the same time, the value is also modified using final. Then the value cannot be modified inside the String class. That is to say, once the value is assigned the initial value, the address pointed to by the value cannot be changed. However, the content of final modification reference type variable mentioned above, this can only ensure that the value cannot point to other objects, but the state of the object pointed to by the value can be changed.

By looking at the String class source code, we can find that the String class is immutable. The key is that SUN engineers are careful not to move the elements in the character array in all the String methods. Therefore, the key to the immutability of the String class lies in the underlying implementation, not just a final.

3. Modify String to make it "mutable"

Although the value is final modified, it just means that the value cannot be re-pointed to other references. However, the array pointed to by value can be changed. Generally, we cannot access the elements of the array pointed to by this value. But, reflection, yes, reflection is OK, it's awesome. The value attribute in the String object can be reflected, and the structure of the array can be changed through the obtained value reference.

public static void main(String[] args) throws Exception {
  String str = "Hello World";
  ("Str before modification:" + str);
  ("Memory address of str before modification" + (str));
  // Get the value field in the String class  Field valueField = ("value");
  // Change the access permissions of the value attribute  (true);
  // Get the value of the value attribute on the str object  char[] value = (char[]) (str);
  // Change the characters in the array referenced by the value  value[3] = '?';
  ("Modified str:" + str);
  ("Memory address of str before modification" + (str));
}

// Run results// You can see that str's string sequence has been changed, but str's memory address has not changed.Before modificationstr:Hello World
Before modificationstrMemory address1922154895
Modifiedstr:Hel?o World
Before modificationstrMemory address1922154895

4. Reasons for String design to immutability

In Java, designing String as immutable is a result of comprehensively taking into account various factors such as memory, synchronization, data structure and security. The following will summarize various factors.

1. The need for constant pool during runtime

For example, when executing String s = "abc"; when executing the above code, the JVM first checks whether the String object "abc" exists in the runtime constant pool. If the object already exists, it does not need to create a new String object "abc", but instead points the reference s directly to the String object "abc" that already exists in the runtime constant pool; if the object does not exist, first create a new String object "abc" in the runtime constant pool, and then point the reference s to the new String object created in the runtime constant pool.
In this way, only a String object "abc" will be created in the runtime constant pool, which saves memory space.

2. Synchronization

Because String objects are immutable, they are multi-thread-safe, and the same String instance can be shared by multiple threads. This way you don't need to use synchronization due to thread safety issues.

3. Allow String objects to cache hashcode

Looking at the source code of the String class in JDK1.8 above, you can find that there is a field hash. The immutability of the String class ensures the uniqueness of the hashcode, so you can use the hash field to cache the hashcode of the String object, so there is no need to recalculate the hashcode every time. Therefore, String objects in Java are often used as keys for containers such as HashMap.

4. Safety

If the String object is mutable, it can cause serious security problems. For example, the username and password of the database are passed in as a string to obtain the database connection, or in socket programming, the host name and port are passed in as a string. Because the String object is immutable, its value is immutable. Otherwise, hackers can exploit loopholes and change the value of the object pointed to by the String reference, causing security vulnerabilities.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.