Here are a set of diagrams to illustrate Java String's immutability.
1. Declare a string
String s = new String("abcd");
s stores the reference of the string object. The arrow below should be interpreted as "store reference of".
2. Assign one string variable to another string variable
String s2 = s;
s2 stores the same reference value, since it is the same string object.
3. Concat string
s = s.concat("ef");
s now stores the reference of newly created string object.
Summary
Once a string is created in memory(heap), it can not be changed. We should note that all methods of String do not change the string itself, but rather return a new String.
If we need a string that can be modified, we will need StringBuffer or StringBuilder. Otherwise, there would be a lot of time wasted for Garbage Collection, since each time a new String is created.
In Java, a string can be created by using two methods:
String x = "abc";
String y = new String("abc");
What is the difference between using double quotes and using constructor?
This question can be answered by using two simple code examples.
Example 1:
String a = "abcd";
String b = "abcd";
System.out.println(a == b); // True
System.out.println(a.equals(b)); // True
a==b
is true because
a
and
b
are referring to the same string literal in the
method area. The memory references are the same.
When the same string literal is created more than once, only one copy of each distinct string value is stored. This is called "string interning". All compile-time constant strings in Java are automatically interned.
Example 2:
String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d); // False
System.out.println(c.equals(d)); // True
c==d
is false because
c
and
d
refer to two different objects in the heap. Different objects always have different memory references.
This diagram illustrate the two situations above:
The substring(int beginIndex, int endIndex)
method in JDK 6 and JDK 7 are different. Knowing the difference can help you better use them. For simplicity reasons, in the followingsubstring()
represent the substring(int beginIndex, int endIndex)
method.
1. What substring() does?
The substring(int beginIndex, int endIndex) method returns a string that starts with beginIndex and ends with endIndex-1.
String x = "abcdef";
x = x.substring(1,3);
System.out.println(x);
Output:
bc
2. What happens when substring() is called?
You may know that because x is immutable, when x is assigned with the result of x.substring(1,3), it points to a totally new string like the following:
However, this diagram is not exactly right or it represents what really happens in the heap. What really happens when substring() is called is different between JDK 6 and JDK 7.
3. substring() in JDK 6
String is supported by a char array. In JDK 6, the String class contains 3 fields: char value[], int offset, int count. They are used to store real character array, the first index of the array, the number of characters in the String.
When the substring() method is called, it creates a new string, but the string's value still points to the same array in the heap. The difference between the two Strings is their count and offset values.
The following code is simplified and only contains the key point for explain this problem.
//JDK 6
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
//check boundary
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
4. A problem caused by substring() in JDK 6
If you have a VERY long string, but you only need a small part each time by using substring(). This will cause a performance problem, since you need only a small part, you keep the whole thing. For JDK 6, the solution is using the following, which will make it point to a real sub string:
x = x.substring(x, y) + ""
5. substring() in JDK 7
This is improved in JDK 7. In JDK 7, the substring() method actually create a new array in the heap.
//JDK 7
public String(char value[], int offset, int count) {
//check boundary
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
//check boundary
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}