As we all know, String is an immutable, final modified class. So what is the immutability of it? Take a look at the following simple code:

  String str= "123";
  str = "456";
Copy the code

I’m sure no one thinks this code is wrong, so does this comply with String immutability? How is the immutability of strings represented? What are the benefits of immutability? With these questions in mind, read the fuck Source code!

What is an immutable class?

Effective Java minimizes variability in immutable classes

Immutable classes are simply classes whose instances cannot be modified. All information contained in each instance must be provided at the time the instance is created and remains constant throughout the life of the object. To make a class immutable, follow these five rules:

1. Do not provide any methods that modify the state of the object.

2. Ensure that classes are not extended. It is common practice to call the class final to prevent subclassing from breaking the immutable behavior of the class.

3. Make all fields final.

4. Make all fields private. Prevents clients from gaining access to mutable objects referenced by the domain and from modifying those objects directly.

5. Ensure mutually exclusive access to any mutable components. If a class has fields that point to mutable objects, you must ensure that clients of that class cannot obtain references to those objects.

In the Java platform class library, there are many immutable classes, such as String, primitive wrapper classes, BigInteger, BigDecimal, and so on. In summary, immutable classes have some notable general characteristics: classes themselves are final; Almost all fields are private final; Methods that can modify object properties are not exposed. By looking at the String source code, you can clearly see these characteristics.

Why is String immutable?

String source code implementation

Going back to the question posed at the beginning, for this code:

String str= "123";
str = "456";
Copy the code

The following diagram clearly illustrates the code execution:

When the first line of code is executed, a new object instance 123 is created on the heap, and STR is a reference to the instance that contains nothing more than the memory address of the instance on the heap. The second line simply changes the address of the STR reference to another instance, 456. So, as mentioned earlier, immutable classes are just classes whose instances cannot be modified. Reassigning STR simply changes its reference, and does not actually change its original memory address. This benefit is also obvious, especially when there are multiple String references to the same memory address. Changing the value of one reference does not affect the value of the other references. Of course, there are some other benefits, which will be summarized later.

So how does a String remain immutable? Combined with the five principles outlined in Effective Java, it becomes clear when you read the source code.

Take a look at the fields of String:


public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // use serialVersionUID from JDK 1.0.2for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/.. /platform/serialization/spec/output.html"< p style = "max-width: 100%; clear: both; min-height: 1em;"Stream Elements"</a> */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; . . }Copy the code

The String class is final, fulfilling the second principle: the guarantee that the class will not be extended. Analyze its fields:

  • Private final char Value [] : You can see that Java still uses byte arrays to implement strings, and final to ensure immutability. This is why String instances are immutable.

  • Private int Hash: String hash cache

  • Private Static Final Long serialVersionUID = -6849794470754667710L: String serialVersionUID of the object

  • Private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0

The main field is value, which represents the value of a String object. Because of the private final modifier, there is normally no way for the outside world to modify its value. As the third rule makes all fields final. And article 4 make all fields private as described. Can such a private plus final guarantee everything? Take a look at the following code example:

    final char[] value = {'a'.'b'.'c'};
    value[2] = 'd';
Copy the code

The value object is already a, B, and D in memory. In fact, final only modifies the reference value. You can’t point value to any other memory address. For example, the following code will not compile:

    final char[] value = {'a'.'b'.'c'};
    value = {'a'.'b'.'c'.'d'};
Copy the code

So there is no way to guarantee immutability just by having a final, and there is no way to guarantee immutability if the class itself provides methods to modify instance values. The first rule in Effective Java is not to provide any methods that modify the state of an object. The String class does this very well. There are many functions that operate on strings in strings, such as substring concat replace replaceAll, etc. Do these functions modify the value field in the class? Let’s look at the internal implementation of the concat() function:

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}
Copy the code

Note that each of these implementations does not have any effect on value. Arrays.copyof () is used to get a copyOf the value, and then a new String is used as the return value. The other methods are the same as contact and adopt similar methods to ensure that value will not be changed. Indeed, the String class does not provide any methods to change its value. This ensures that strings are immutable more than final.

The position of the String in memory

For more information about the Java memory region, read the chapter in Understanding the Java Virtual Machine or my reading notes.

The observant reader will notice that there are two ways to write strings:

  String str1 = "123";
  String str2 = new String("123");
  System.out.println(str1 == str2);
Copy the code

The result is clearly false. We all know the concept of string constant pools, but the JVM maintains a string constant pool specifically to reduce the number of string objects created for reuse. The first literal version looks directly for the value 123 in the String constant pool, returns a reference to the value if there is one, and creates a String with the value 123 if not and stores it in the String constant pool. With the new keyword, a new String is generated directly on the heap, regardless of whether the value is in the constant pool. So essentially str1 and STR2 point to different memory addresses.

So, can a String generated with the new keyword enter the String constant pool? The answer is yes, the String class provides a native method that intern() uses to add this object to the String constant pool:

  String str1 = "123";
  String str2 = new String("123");
  str2=str2.intern();
  System.out.println(str1 == str2);
Copy the code

Prints true. After str2 calls intern(), it first looks for an object with the value 123 in the string constant pool. If it does, it returns a reference to the object. If it does not, it adds str2 and returns. If a str1 object with the value 123 already exists in the constant pool, the reference address of STR1 is returned so that str1 and STR2 point to the same memory address.

So where is the string constant pool in memory? The constant pool is the part of the method area that holds various literal and symbolic references generated at compile time, with the possibility of putting new constants into the pool at run time. The Method area is described as a logical part of the Heap in the Java Virtual Machine specification, but it has a separate name, non-heap, supposedly to distinguish it from the Java Heap.

Benefits of immutable classes

Immutable classes are summarized in Effective Java.

  • Immutable classes are simpler.
  • Immutable objects are thread-safe in nature; they do not require synchronization. Immutable objects can be freely shared.
  • Not only can immutable objects be shared, but even their internal information can be shared.
  • Immutable objects provide a lot of build for other objects.
  • The only real disadvantage of immutable classes is that you need a separate object for each different value.

Is String really immutable?

So, String is an immutable class, but is there really no way to change the value of a String? The answer is definitely no. Reflexes can do a lot of things they normally can’t.

String str = "123";
System.out.println(str);
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[]) field.get(str);
value[1] = '3';
Copy the code

Execution Result:

123, 133,Copy the code

Obviously, the immutability of a String can be broken by reflection.

conclusion

In addition to the String class, the system class library provides other immutable classes, wrapper classes for primitive types, BigInteger, BigDecimal, and so on. These immutable classes are easier to design, implement, and use, less error-prone, and more secure than mutable classes. Also, keep in mind that immutability depends not just on a final keyword, but on implementation details inside the class.

Article update on wechat public number: Bingxin said, focus on Java, Android original knowledge sharing, LeetCode solution, welcome to pay attention to!