This article is participating in “Java Theme Month – Java Development Practice”, for details: juejin.cn/post/696826…

This is the 9th day of my participation in Gwen Challenge

The premise is introduced

The String class is used quite frequently in Java. It is the core class in the Java language. Under the java.lang package, it is mainly used for string comparison, lookup, concatenation, and so on. The best way to understand a class in depth is to look at the source code:

What is a string

A string is a sequence of characters enclosed in quotation marks.

String class (String)

/** String class source */
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; // Default to 0
    /** use serialVersionUID from JDK 1.0.2 for 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. The HTML "> * Object serialization Specification, Section 6.2," Stream Elements "< / a > * /
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0]; ... }Copy the code

From the source, you can see the following points:

  • The String class is decorated with the final keyword, which means that the String class cannot be inherited, and that its properties and methods are all decorated with final. Any operation generates a new object.

    • String:: subString(), String::concat(), and so on all generate a new String object and do not operate on the original object.
  • The String class implements the Serializable, CharSequence, and Comparable interfaces.

  • The values of the String class are stored in char arrays, which are private and final and cannot be modified once a String is created.

String immutability

  • Once a String is created, it is immutable. Any changes to the String do not affect the original object, and any related operations generate new objects.

  • The fact that String is immutable is that when we try to assign “abcde” to an existing object “abcd”, String creates a new object.

Pay attention to the point

This cannot be modified merely indicates that the address cannot be modified (i.e., the reference address in the stack called value is immutable; the compiler does not allow us to point value to another address in the heap), not that the contents of the array stored in the heap are immutable.

Since we say that strings are immutable, it is clear that final is not enough:

  1. The char array is private, and the String class provides no way to modify the array, so there is no effective way to change it once it is initialized.

  2. If a String class is modified with a final modifier, we’ll start with the function of the final modifier class. A class modified with a final modifier cannot be inherited. All member methods in the class are implicitly specified as final methods. That is, you cannot have subclasses, and member methods cannot be overridden. ;

  3. All of the String methods are careful not to modify the data in the char array. All operations that involve modifying the data in the char array create a new String.

For example, the substring method:
public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
Copy the code

Why make it immutable?

Strings are designed to be immutable because of the String constant pool.
  • Definition of a string constant pool

    • Creating a large number of strings frequently will greatly affect the performance of the program. String allocation, like other object allocation, is expensive in time and space, and we use a lot of strings. In order to improve performance and reduce memory overhead, the JVM optimizes using string constant pools when instantiating strings.
  • The JVM implements some optimizations when instantiating string constants to improve performance and reduce memory overhead:

    • String creates a String constant Pool (String Pool, HashSet StringTable). When a String constant is created in the cache, it is checked first to see if the String exists in the String constant Pool.
    • The idea of pooling is not uncommon in Java, and a similar idea is string constant pooling. When creating a string, the JVM first checks the string constant pool and returns a reference to an instance in the constant pool if the string already exists in the pool. If the string does not exist in the constant pool, it is instantiated and placed in the constant pool.
Only one String is created in the heap:
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2) // true 

Copy the code

String is allowed to be changed, so if we change str2 to good, str1 will also be changed, which is obviously not what we want to see.

How many objects does the new String(” ABC “) create?
  • If you haven’t used the “ABC” String before, you’re definitely creating two objects, a String in the heap, and a String constant pool, two of them.

  • If the “ABC” String has already been used, the object is no longer created in the String constant pool. Instead, it is retrieved from the String constant buffer and only a String object is created in the heap.

String s1 = "abc";
String s2 = new String("abc");
//s2 creates only one object
Copy the code
Strings are designed to be immutable for security
  • As the most basic and commonly used data type, String is used as an argument by many Java libraries. If String is not immutable, it is considered safe. String application scenarios, designed to be immutable can effectively prevent string tampering.

  • String is used as a parameter by many Java classes (libraries), such as network connection URL, file path, and String parameters required by reflection mechanism, etc. If String is not fixed, it will cause various security risks.

  • In a multi-threaded environment, it is known that it is dangerous for multiple threads to modify the same resource at the same time, while String, as an immutable object, cannot be modified, and there is no problem for multiple threads to read the same resource at the same time. Therefore, String is thread-safe.

Strings are designed to be immutable for efficiency

String invariance ensures the uniqueness of the hash code, so it can be safely cached. This is also a performance optimization method, which means that you don’t have to compute a new hash code every time.


Is String really immutable?

  • A String simply changes the contents of a char array value, and value is a private property. Is there any way in Java to access a class’s private property?

  • Reflection, you can use reflection to modify the contents of a char array directly, but normally we don’t do that.

Look at the following code

Replace of string

public String replace(char oldChar, char newChar) {
    if(oldChar ! = newChar) {int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        while (++i < len) {
            if (val[i] == oldChar) {
                break; }}if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            // Create a new string return
            return new String(buf, true); }}return this;
}
Copy the code

The same goes for the other methods. The sub, concat, or replace operations are not performed on the original string, but instead generate a new string object.

String splicing

Concatenation of strings is a common operation in Java, but concatenation of strings requires more than just a “+” sign, and there are a few things to watch out for that can be inefficient.

public static void main(String[] args) throws Exception {
    String s = "";
    for (int i = 0; i < 10; i++) {
        s+=i;
    }
    System.out.println(s);/ / 0123456789
}
Copy the code

What’s wrong with using += concatenation strings inside loops? Let’s decompile it and see what happens.

  • After decompilation, we can see that the underlying “+=” concatenation of the String class is the use of StringBuilder, which initializes a StringBuilder object, then concatenates it using the append() method, and finally yields the result using the toString() method.

  • The problem is that using += concatenation in the body of the loop creates many temporary StringBuilder objects that are then assigned to the original String by calling toString(). This can generate a large number of temporary objects, which can seriously affect performance.

Therefore, it is recommended to use the StringBuilder or StringBuffer class for string concatenation in the loop body. Examples are as follows:

public static void main(String[] args) throws Exception {
    StringBuilder s = new StringBuilder();
    for (int i = 0; i < 10; i++) {
        s.append(i);
    }
    System.out.println(s.toString());/ / 0123456789
}
Copy the code
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

The difference between StringBuilder and StringBuffer is that StringBuffer’s methods are both decorated with the sync keyword and are therefore thread-safe, whereas StringBuilder is thread-unsafe (efficient).

conclusion

It is not because char arrays are final that strings are immutable; it is because strings are designed to be immutable that char arrays are made final.

All immutable classes follow these rules exactly:

  • Do not provide setter methods (including methods that modify fields and methods that modify field reference objects);

  • Define all fields of a class as final, private;

  • Subclasses are not allowed to override methods. The easy way is to declare the class final. A better way is to declare the constructor private and create the object through the factory method.

  • If the class field is a reference to a mutable object, it is not allowed to modify the referenced object.