Java basics (5) In-depth analysis of String properties

introduction

This article will explain several properties of String.

First, the immutability of String

For starters, it's easy to mistake String objects as mutable, especially when +chained, the objects seem to actually change. However, String objects cannot be modified once created. Next, we will analyze how String maintains its immutable properties ;

1. Method 1: final classes and final private members

Let's first look at some of the source code 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; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

  }

  We can find that String is a final class, and the three members are private , which means that String cannot be inherited, which prevents it from happening: Programmers make String class by inheriting the methods of overriding String class. is the "variable" case.

  It is found from the source code that each String object maintains a char array - a private member value. The array value is the underlying array of String, which is used to store the content of the string, and is private final , but the array is a reference type, so it can only limit the reference to not change, that is to say, the value of the array element can be changed, and String There is a constructor that can pass in an array, so can we "modify" the content of String by modifying the elements of the external char array?

Let's do an experiment as follows:

public static void main(String[] args) {
        
        char[] arr = new char[]{'a','b','c','d'};       
        String str = new String(arr);       
        arr[3]='e';     
        System.out.println("str= "+str);
        System.out.println("arr[]= "+Arrays.toString(arr));
    }

operation result

str= abcd arr[]= [a, b, c, e]

  The result was not what we expected. The string str uses the array arr to construct an object. When the array arr modifies its element value, the string str does not change. Let's see how this constructor works:

public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

  It turns out that when String uses an external char array to construct an object, it copies an external char array again , so that changes in the external char array will not affect the String object.

2. Method 2: Change the method that creates the object

  From the above analysis, we know that we cannot modify the String object from the outside, so it is impossible to use the methods provided by String, because there are many methods that seem to be able to change the String object, such as replace(), replaceAll(), and so substring()on. Let's take a substring()look at the source code as an example:

public String substring(int beginIndex, int endIndex) {
        //........
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

As can be seen from the source code, if the entire string is not cut, a new object will be created. That is to say, as long as it is not equal to the original string, a new String object will be created .

expand

The wrapper classes of basic types are very similar to String. They are both final classes, immutable objects, and private final members that maintain a stored content. Such as Integer class:

public final class Integer extends Number implements Comparable<Integer> {
   
     private final int value;
}

Second, String + operation and string constant pool

Let's look at an example first:

public class MyTest {
    public static void main(String[] args) {
        
        String s = "Love You";      
        String s2 = "Love"+" You";
        String s3 = s2 + "";
        String s4 = new String("Love You");
        
        System.out.println("s == s2 "+(s==s2));
        System.out.println("s == s3 "+(s==s3));
        System.out.println("s == s4 "+(s==s4));
    }
}

operation result:

s == s2  true s == s3  false s == s4  false

  Are you confused about the results of the operation? Don't worry, we'll figure it out slowly. First of all, we need to know that the compiler has an advantage: it optimizes the code as much as possible during compilation, so the calculation that can be done by the compiler will not wait for the runtime calculation, such as the calculation of constant expressions, which is done during compilation time. . Therefore, the result of s2 is actually calculated during compilation, which is the same as the value of s, so the two are equal, that is, both belong to literal constants, which are created and maintained in the string constant pool when the class is loaded. However, the expression of s3 contains variable s2, and the calculation can only be performed at runtime, that is, the result is calculated at runtime, and the object created in the heap is naturally not equal to s. And s4 uses new to create objects directly in the heap, and it is even less likely to be equal.

  How to complete the + sign chaining operation of String during runtime, you must know that the String object is an immutable object. We use the jad command jad MyTest.class to decompile the calss file of the above example back to the java code to see how it is implemented:

public class MyTest
{

    public MyTest()
    {
    }

    public static void main(String args[])
    {
        String s = "Love You";
        String s2 = "Love You";//已经得到计算结果
        String s3 = (new StringBuilder(String.valueOf(s2))).toString();
        String s4 = new String("Love You");
        System.out.println((new StringBuilder("s == s2 ")).append(s == s2).toString());
        System.out.println((new StringBuilder("s == s3 ")).append(s == s3).toString());
        System.out.println((new StringBuilder("s == s4 ")).append(s == s4).toString());
    }
}

  It can be seen that the compiler treats the + sign as a StringBuilder.append()method. That is to say, during runtime, the calculation of the linked string is done by creating a StringBuilder object and calling append()the method, and each expression of the linked string must create a StringBuilder object. Therefore , when performing string chaining repeatedly in a loop, you should consider using StringBuilder directly instead of + chaining to avoid the performance overhead of repeatedly creating StringBuilder.

String constant pool

For the constant pool, you can refer to my previous article. I will not go into depth here, but only explain the part related to String.

  The contents of the string constant pool are mostly derived from the compiled string literal constants. also increases during operation,

String intern():

Returns the normalized representation of a string object. An initially empty string pool maintained privately by class String. When the intern method is called, if the pool already contains a string equal to this String object (as determined by the equals(Object) method), the string in the pool is returned. Otherwise, add this String object to the pool and return a reference to this String object. It follows the following rule: for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

Another point worth noting is that although the return value of String.intern() is always equal to the string constant. But this does not mean that the intern() return of the same string will be the same at every moment of the system (although in more than 95% of the cases, it will be the same). Because there is such a possibility: after an intern() call, the string is recycled at a certain time, and after another intern() call, the string with the same literal value is added to the constant pool again, but the reference The location has been different.

3. The hashcode() method of String

  String also complies with the standard of equals, that s.equals(s1)is , if it is true, it is s.hashCode()==s1.hashCode()also true. I don't focus on the eqauls method here, but explain the hashCode()method , which is interesting String.hashCode()and may also be asked in interviews. Let's take a look at the code first:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

##Why choose 31 as the multiplier?

From the information on the Internet, there are generally two reasons:

  • 31 is a large and small prime number, which is one of the preferred prime numbers as the hashCode multiplier. Other similar prime numbers, such as 37, 41, 43, etc., are also good choices. So why did you choose 31? See the second reason.

  • 31 can be optimized by the JVM, 31 * i = (i << 5) - i.

Source: http://www.cnblogs.com/jinggod/p/8425182.html

If the article is inappropriate, please correct me. You can also pay attention to my WeChat public account: 好好学java, to obtain high-quality resources.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325010822&siteId=291194637