Why is String in Java immutable? -- String source code analysis

What is an immutable object?

    As we all know, in Java, String class is immutable. So what exactly is an immutable object? Think of it this way: An object is immutable if it cannot change its state after it is created. Cannot change the state means that the member variables in the object cannot be changed, including the value of the basic data type, the variable of the reference type cannot point to other objects, and the state of the object pointed to by the reference type cannot be changed.

Distinguish between objects and references to objects

For Java beginners, there is always confusion about String being an immutable object. See the code below:

String s = "ABCabc";  
System.out.println("s = " + s);  
  
s = "123456";  
System.out.println("s = " + s);  

The print result is:

s = ABCabc
s = 123456

First create a String object s, then let the value of s be "ABCabc", and then let the value of s be "123456". As can be seen from the printout, the value of s has indeed changed. So how do you say that String objects are immutable? In fact, there is a misunderstanding here: s is just a reference to a String object, not the object itself. An object is a memory area in memory. The more member variables there are, the more space this memory area occupies. A reference is just a 4-byte data, which stores the address of the object it points to, through which the object can be accessed.

That is to say, s is just a reference, it points to a specific object, when s = "123456"; After this code is executed, a new object "123456" is created, and the reference s points to this heart again The original object "ABCabc" still exists in memory and has not changed. The memory structure is shown in the following figure:

    A difference between Java and C++ is that it is impossible to directly manipulate the object itself in Java. All objects are pointed to by a reference , and the object itself must be accessed through this reference, including obtaining the value of member variables and changing the member variables of the object. Invoke methods of objects, etc. In C++, there are three things: references, objects and pointers, all of which can access objects. In fact, references in Java are conceptually similar to pointers in C++. They are the address values ​​of stored objects in memory, but in Java, references lose some flexibility. For example, references in Java cannot be Addition and subtraction are performed like pointers in C++.

Why are String objects immutable?

To understand the immutability of String, first look at what member variables are in the String class. In JDK1.6, String's member variables are as follows:

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence  
{  
    /** The value is used for character storage. */  
    private final char value[];  
  
    /** The offset is the first index of the storage that is used. */  
    private final int offset;  
  
    /** The count is the number of characters in the String. */  
    private final int count;  
  
    /** Cache the hash code for the string */  
    private int hash; // Default to 0  

In JDK1.7, the String class has made some changes, mainly to change the behavior of the substring method when it is executed, which is not related to the topic of this article. There are only two main member variables of the String class in JDK1.7:

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  

As can be seen from the above code, the String class in Java is actually an encapsulation of a character array. In JDK6, value is an array encapsulated by String, offset is the starting position of String in this value array, and count is the number of characters occupied by String. In JDK7, there is only one value variable, that is, all characters in the value belong to the String object. This change does not affect the discussion in this article. In addition, there is a hash member variable, which is a cache of the hash value of the String object. This member variable is also irrelevant to the discussion in this article. In Java, arrays are also objects (see my previous article on the properties of arrays in java ). So value is also just a reference, it points to a real array object. In fact, after executing String s = "ABCabc"; this code, the real memory layout should be like this:

The three variables value, offset and count are all private, and no public methods such as setValue, setOffset and setCount are provided to modify these values, so String cannot be modified outside the String class. That is, once initialized, it cannot be modified, and these three members cannot be accessed outside the String class. In addition, the three variables value, offset and count are final, that is to say inside the String class, once these three values ​​are initialized, they cannot be changed. So it can be considered that String objects are immutable.

Then in String, there are obviously some methods, and calling them can get the changed value. These methods include substring, replace, replaceAll, toLowerCase, etc. For example the following code:

String a = "ABCabc";  
System.out.println("a = " + a);  
a = a.replace('A', 'a');  
System.out.println("a = " + a);  

The print result is:

a = ABCabc
a = aBCabc

Then the value of a seems to have changed, but it is actually the same misunderstanding. Again, a is just a reference, not a real string object. When calling a.replace('A', 'a'), a new String object is created inside the method, and the heart object is reassigned to Citation a. The source code of the replace method in String can illustrate the problem:

Readers can check other methods by themselves, all of which are to recreate a new String object inside the method, and return this new object, the original object will not be changed. This is why methods like replace, substring, toLowerCase, etc. all have return values. It's also why calling like the following doesn't change the value of the object:

String ss = "123456";  
  
System.out.println("ss = " + ss);  
  
ss.replace('1', '0');  
  
System.out.println("ss = " + ss);  

print result:

ss = 123456
ss = 123456

Are String objects really immutable?

It can be seen from the above that the member variables of String are private final, that is, they cannot be changed after initialization. Then among these members, value is special because it is a reference variable, not a real object. value is final modified, that is to say final can no longer point to other array objects, so can I change the array pointed to by value? For example, change a character at a certain position in the array to an underscore "_". At least we can't do it in our own ordinary code, because we can't access this value reference at all, let alone modify the array through this reference.

So how can I access private members? That's right, using reflection, you can reflect the value property in the String object , and then change the structure of the array through the obtained value reference. Here is the example code:

public static void testReflection() throws Exception {  
      
    //创建字符串"Hello World", 并赋给引用s  
    String s = "Hello World";   
      
    System.out.println("s = " + s); //Hello World  
      
    //获取String类中的value字段  
    Field valueFieldOfString = String.class.getDeclaredField("value");  
      
    //改变value属性的访问权限  
    valueFieldOfString.setAccessible(true);  
      
    //获取s对象上的value属性的值  
    char[] value = (char[]) valueFieldOfString.get(s);  
      
    //改变value所引用的数组中的第5个字符  
    value[5] = '_';  
      
    System.out.println("s = " + s);  //Hello_World  
}  

The print result is:

s = Hello World
s = Hello_World

In this process, s always refers to the same String object, but before and after reflection, the String object has changed, that is, the so-called "immutable" object can be modified through reflection . But generally we don't do that. This instance of reflection can also illustrate a problem: if an object can change the state of other objects it composes, then this object is probably not immutable . For example, a Car object combines a Wheel object. Although the Wheel object is declared as private final, the internal state of the Wheel object can be changed, so the immutability of the Car object cannot be well guaranteed.

-----------------------------------------------------------------------------------------------------------

Test code written by shilvfei:

package string.test;

import java.lang.reflect.Field;

/**
 * @description
 * 
 * @author shilvfei
 * 
 * @date 2018年5月7日
 */
public class StringTest {

	public static void main(String[] args) throws Exception {
		//test01();
		test02();
	}

	private static void test01() {
		String s = "abc";
		System.out.println("替换前s:"+s);
	
		String newS = s.replaceAll(s,"def");
		System.out.println("替换后s:"+s);
		System.out.println("newS:"+newS);
	}
	
	//String 真的是不可变的吗?
	private static void test02() throws NoSuchFieldException, IllegalAccessException {
		String s = "hello World";
		System.out.println("s = "+s);
		
		//获取String类的value
		Field valueFieldOfString  = String.class.getDeclaredField("value");
		 //改变value属性的访问权限  
		valueFieldOfString .setAccessible(true);
		//获取s对象上的value属性的值  
		char[] value = (char[]) valueFieldOfString.get(s);
		//改变value所引用的数组中的第5个字符  
		value[5] = '_';
		System.out.println("s = "+s);
	}
	
	
}

 

Guess you like

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