一直有寫博客的打算换淆,由于種種原因沒有開始,今天在公司正好討論到了String這個特殊的類舔涎,準備開啟自己的博客之旅。
作為Java語言中應用最廣泛的String類逗爹,也可以算得上是最特殊的一個類亡嫌,我們非常有必要深入了解它。
在《Thinking in java》第四版中有提到掘而,“String類中每一個看起來會修改String值的方法挟冠,實際上都是創(chuàng)建了一個全新的String對象,以包含修改后的字符串內容镣屹。而最初的String對象則絲毫未動圃郊。”
我們來看一下最常見的String方法replace(char oldChar女蜈, char newChar)持舆。
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++;
}
return new String(buf, true);
}
}
return this;
}
在源碼中我們可以很明顯的看到replace方法調用之后返回的是一個新的String對象色瘩,也就是原來的String對象根本沒有改變。
我們可以做一個小測試逸寓。
public static void main(String[] args) {
String a = "abc";
StringBuilder sb = new StringBuilder("ccc");
System.out.println(new Test1().test(a));
System.out.println(a);
System.out.println(new Test1().test2(sb));
System.out.println(sb);
}
//不可變的String
public String test(String a) {
a += "bb";
return a;
}
//可變的StringBuilder
public StringBuilder test2(StringBuilder sb) {
return sb.append("xx");
}/* Output
abcbb
abc
cccxx
cccxx
*/
通過對比我們可以發(fā)現原來的String對象并沒有發(fā)生改變居兆,返回的是一個新的String對象,而可變的StringBuilder在進行方法調用之后竹伸,原來的對象已經發(fā)生了改變泥栖。
翻開JDK源碼。
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
...
}
首先Strng類用final修飾勋篓,說明無法繼承String吧享。再看下面String類的主成員字段是一個value字符數組,用final修飾譬嚣,不可改變钢颂。不過雖然不能改變,也只是無法改變value這個引用地址拜银。指向的內容依然是可以改變的殊鞭。除此之外還有一個hash變量,是該String對象的哈希值緩存尼桶〔俨樱看一下例子,
public static void main(String[] args) {
final char value[] = { 'a', 'b', 'c' };
char another[] = { 'e', 'f', 'g' };
value = another;
}
編譯器間報錯泵督,編譯器不允許我把value的引用指向heap內存中另外的地址趾盐。不過只要改變數組元素,就可以搞定幌蚊。
public static void main(String[] args) {
final char value[] = { 'a', 'b', 'c' };
value[0] = 'b';
System.out.println(value);
}/* Output:
bbc
*/
value字符數組內容已經被改變了谤碳。
所以String不可變,其實是因為String方法沒有動value數組的元素溢豆,沒有暴露內部成員字段。String被final修飾瘸羡,也導致整個String無法被繼承漩仙,不被破壞。
其實研究到這里犹赖,腦海中已經有了一個大膽的想法队他,雖然value數組引用沒有暴露,通過一般途徑無法獲取到峻村,不過我們大可以用反射來訪問私有成員麸折。
public static void testReflection() throws Exception {
//創(chuàng)建字符串"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
}
在這個過程中s引用始終指向同一個對象粘昨,在反射前后垢啼,這個對象被改變了窜锯,也就是通過反射可以修改所謂的“不可變”對象。