What are immutable objects?
A String object is immutable, but that just means you can't change its value by calling its public methods.
As we all know, in Java, String class is immutable. So what exactly are immutable objects? You can think of it this way: If an object cannot change its state after it is created, then the object is immutable. The state cannot be changed, which means that the member variables within the object cannot be changed, including the values of basic data types. Variables of reference types cannot point to other objects, and the state of objects pointed to by reference types cannot be changed.
Distinguish between objects and object references
For Java beginners, there are always doubts about String being an immutable object. Look at the code below:
String s = "ABCabc";System.out.println("s = " + s);s = "123456";System.out.println("s = " + s);The printed 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 printed results, the value of s has indeed changed. So why do you still 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. The object is a memory area in the memory. The more member variables, the larger the space this memory area occupies. A reference is just a 4-byte data that stores the address of the object it points to. The object can be accessed through this address.
In other words, s is just a reference, which 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. object, the original object "ABCabc" still exists in the memory and has not changed. The memory structure is shown in the figure below:
One difference between Java and C++ is that in Java it is impossible to directly operate the object itself. All objects are pointed to by a reference. You must use this reference to access the object itself, including obtaining the value of the member variable and changing the member variable of the object. Call object methods, etc. In C++, there are three things: references, objects and pointers, all of which can access objects. In fact, references in Java and pointers in C++ are conceptually similar. They are the address values of stored objects in memory. However, in Java, references lose some flexibility. For example, references in Java cannot be used like Addition and subtraction are performed like pointers in C++.
Why are String objects immutable?
To understand the immutability of String, first take a look at the member variables in the String class. In JDK1.6, the member variables of String include the following:
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 0In JDK1.7, the String class has made some changes, mainly changing the behavior when the substring method 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 0As 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 the 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 value belong to the String object. This change does not affect the discussion of 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 of this article. In Java, arrays are also objects (please refer to my previous article Characteristics of Arrays in Java). So value is just a reference, which points to a real array object. In fact, after executing the code String s = “ABCabc”;, 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 to say, 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 all final, which means that within the String class, once these three values are initialized, they cannot be changed. So the String object can be considered immutable.
So 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 printed result is:
a = ABCabca = aBCabc
Then the value of a seems to have changed, but in fact it is the same misunderstanding. Again, a is just a reference, not a real string object. When calling a.replace('A', 'a'), the method internally creates a new String object and reassigns the new object to Cited a. The source code of the replace method in String can illustrate the problem:
Readers can check other methods by themselves. They all re-create 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. This is also why calling like the following does not change the value of the object:
String ss = "123456";System.out.println("ss = " + ss);ss.replace('1', '0');System.out.println("ss = " + ss);Print the result:
ss = 123456 ss = 123456
Are String objects really immutable?
As can be seen from the above, the member variables of String are private final, that is, they cannot be changed after initialization. Among these members, value is special because it is a reference variable, not a real object. value is modified by final, which means that final can no longer point to other array objects, so can I change the array that value points to? For example, change the character at a certain position in the array to an underscore "_". At least we can't do it in the ordinary code we write ourselves, because we can't access this value reference at all, let alone modify the array through this reference.
So how can we access private members? That's right, using reflection, you can reflect the value attribute 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 { //Create the string "Hello World" and assign it to the reference s String s = "Hello World"; System.out.println("s = " + s); //Hello World //Get the value field in the String class Field valueFieldOfString = String.class.getDeclaredField("value"); //Change the access permission of the value attribute valueFieldOfString.setAccessible(true); //Get the value of the value attribute on the s object char[] value = (char[]) valueFieldOfString.get(s); //Change the fifth character in the array referenced by value value[5] = '_' ; System.out.println("s = " + s); //Hello_World }The printed 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 changes. In other words, the so-called "immutable" object can be modified through reflection. But generally we don't do this. This example of reflection can also illustrate a problem: if the state of an object and other objects it is composed of can change, then this object is probably not an immutable object. For example, a Car object is combined with a Wheel object. Although the Wheel object is declared private final, the internal state of the Wheel object can change, so the Car object cannot be guaranteed to be immutable.