java String 可變性的分析
前言
這兩天在看Java面試相關(guān)的一些問題,很偶然也很幸運(yùn)的看到了下面這篇文章。
http://www.dbjr.com.cn/article/105448.htm
這篇文章的作者有一系列關(guān)于Java深入學(xué)習(xí)的文章,很值得一看,個人覺得非常好,很有收獲。
起因

正如我們所理解的,通過
String hello = "Hello World!";
和
String xx = new String("Hello World!");
得到的字符串對象是不一樣的,new方式是在堆空間中創(chuàng)建的,而直接的字符串則是先被放到常量池中。如果有新的與之一樣的對象被創(chuàng)建,則直接讓這個新對象引用常量池中的這個地址即可。
這樣的好處就是可以最大限度的節(jié)省內(nèi)存空間。
而使用new方式創(chuàng)建的則就不一樣了,只要是用了new創(chuàng)建字符串,就會在堆空間中開辟出一塊內(nèi)存,然后返回這個內(nèi)存地址的引用。所以這樣創(chuàng)建的對象,即使內(nèi)容一致,也不會是指向同一個內(nèi)存地址。
下面用幾個簡單的代碼做下測試。
/** *字符串中對于內(nèi)容和地址的判定可以用下面兩種方式,但側(cè)重點不一樣。 */ equals // 判斷 兩個字符串的內(nèi)容是否一致 == // 判斷兩個字符串的內(nèi)存地址是否一致
且看下面的代碼:
public static void simple() {
String s1 = "Hello World!";
String s2 = "Hello World!";
String s3 = new String("Hello World!");
String s4 = new String("Hello World!");
// 下面開始比較引用和內(nèi)容的比較
System.out.println("字符串賦值方式:");
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
System.out.println("\n字符串賦值方式和new方式:");
System.out.println(s1==s3);
System.out.println(s1.equals(s3));
System.out.println("\nnew 方式:");
System.out.println(s3==s4);
System.out.println(s3.equals(s4));
}
得到的結(jié)果如下:
字符串賦值方式: true true 字符串賦值方式和new方式: false true new 方式: false true
結(jié)果卻是和我們所說的那樣。
深入源碼
不出所料,String確實是“不可變的”,每次改變底層其實都是創(chuàng)建了一個心的字符串對象,然后賦予了新值。
為什么會這樣呢?我們也許可以在源碼中找到真相。

哦,原來Java對于String類只是維護(hù)了一個final類型的字符數(shù)組啊。怪不得賦值之后就不能改變了呢。
但是也許你會有疑問,咦,不對啊,“我經(jīng)常使用String的什么replace方法改變字符串的內(nèi)容啊。你這則么解釋呢?”
其實答案還是那樣,它真的沒變,我們并沒有看到事情的真相,相信看完下面的源碼,你就明白了。
/**
* Returns a string resulting from replacing all occurrences of
* {@code oldChar} in this string with {@code newChar}.
* <p>
* If the character {@code oldChar} does not occur in the
* character sequence represented by this {@code String} object,
* then a reference to this {@code String} object is returned.
* Otherwise, a {@code String} object is returned that
* represents a character sequence identical to the character sequence
* represented by this {@code String} object, except that every
* occurrence of {@code oldChar} is replaced by an occurrence
* of {@code newChar}.
* <p>
* Examples:
* <blockquote><pre>
* "mesquite in your cellar".replace('e', 'o')
* returns "mosquito in your collar"
* "the war of baronets".replace('r', 'y')
* returns "the way of bayonets"
* "sparring with a purple porpoise".replace('p', 't')
* returns "starring with a turtle tortoise"
* "JonL".replace('q', 'x') returns "JonL" (no change)
* </pre></blockquote>
*
* @param oldChar the old character.
* @param newChar the new character.
* @return a string derived from this string by replacing every
* occurrence of {@code oldChar} with {@code 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;
}
源碼中很明確的使用了
new String(buf, true);
的方式返回給調(diào)用者新對象了。
真的不可變嗎?
讀到上面的內(nèi)容,其實基本上已經(jīng)夠了。但是了解一下更深層次的內(nèi)容,相信對我們以后編程來說會更好。
源碼中清楚的使用char[] value來盛裝外界的字符串?dāng)?shù)據(jù)。也就是說字符串對象的不可變的特性,其實是源自value數(shù)組的final特性。
那么我們可以這么想,我們不改變String的內(nèi)容,而是轉(zhuǎn)過頭來改變value數(shù)組的內(nèi)容(可以通過反射的方式來修改String對象中的private屬性的value),結(jié)果會怎樣呢?
答案是真的會變哦。
可以先看下下面的代碼
private static void deep() throws NoSuchFieldException, IllegalAccessException {
String hello = "Hello World!";
String xx = new String("Hello World!");
String yy = "Hello World!";
/**
* 判斷字符串是否相等,默認(rèn)以內(nèi)存引用為標(biāo)準(zhǔn)
*/
System.out.println(hello == xx);
System.out.println(hello == yy);
System.out.println(xx == yy);
// 查看hello, xx, yy 三者所指向的value數(shù)組的真實位置
Field hello_field = hello.getClass().getDeclaredField("value");
hello_field.setAccessible(true);
char[] hello_value = (char[]) hello_field.get(hello);
System.out.println( hello_field.get(hello));
Field xx_field = xx.getClass().getDeclaredField("value");
xx_field.setAccessible(true);
char[] xx_value = (char[]) xx_field.get(xx);
System.out.println(xx_field.get(xx));
Field yy_field = yy.getClass().getDeclaredField("value");
yy_field.setAccessible(true);
char[] yy_value = (char[]) yy_field.get(yy);
System.out.println(yy_field.get(yy));
/**
* 經(jīng)過反射獲取到這三個字符串對象的最底層的引用數(shù)組value,發(fā)現(xiàn)如果一開始內(nèi)容一致的話,java底層會將創(chuàng)建的字符串對象指向同一個字符數(shù)組
*
*/
// 通過反射修改字符串引用的value數(shù)組
Field field = hello.getClass().getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[]) field.get(hello);
System.out.println(value);
value[5] = '^';
System.out.println(value);
// 驗證xx是否被改變
System.out.println(xx);
}
結(jié)果呢?
false true false [C@6d06d69c [C@6d06d69c [C@6d06d69c Hello World! Hello^World! Hello^World!
真的改變了。
而我們也可以發(fā)現(xiàn),hello,xx, yy最終都指向了內(nèi)存中的同一個value字符數(shù)組。這也說明了Java在底層做了足夠強(qiáng)的優(yōu)化處理。
當(dāng)創(chuàng)建了一個字符串對象時,底層會對應(yīng)一個盛裝了相應(yīng)內(nèi)容的字符數(shù)組;此時如果又來了一個同樣的字符串,對于value數(shù)組直接獲取剛才的那個引用即可。(相信我們都知道,在Java中數(shù)組其實也是一個對象類型的數(shù)據(jù),這樣既不難理解了)。
不管是字符串直接引用方式,還是new一個新的字符串的方式,結(jié)果都是一樣的。它們內(nèi)部的字符數(shù)組都會指向內(nèi)存中同一個“對象”(value字符數(shù)組)。
總結(jié)
稍微有點亂,但是從這點我們也可以看出String的不可變性其實仍舊是對外界而言的。在最底層,Java把這一切都給透明化了。我們只需要知道String對象有這點特性,就夠了。
其他的,日常應(yīng)用來說,還是按照String對象不可變來使用即可。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
MybatisPlus查詢條件為空字符串或null問題及解決
這篇文章主要介紹了MybatisPlus查詢條件為空字符串或null問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
Java并發(fā)工具之CountDownLatch使用詳解
這篇文章主要介紹了Java并發(fā)工具之CountDownLatch使用詳解,通過使用 CountDownLatch可以使當(dāng)前線程阻塞,等待其他線程完成給定任務(wù),可以類比旅游團(tuán)導(dǎo)游要等待所有的游客到齊后才能去下一個景點,需要的朋友可以參考下2023-12-12
Java實現(xiàn)異步執(zhí)行的8種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Java實現(xiàn)異步執(zhí)行的8種方式,異步編程不會阻塞程序的執(zhí)行,它將耗時的操作提交給后臺線程或其他執(zhí)行環(huán)境,并立即返回,使得程序可以繼續(xù)執(zhí)行其他任務(wù),需要的朋友可以參考下2023-09-09
基于swagger參數(shù)與實體中參數(shù)不一致的原因分析
這篇文章主要介紹了基于swagger參數(shù)與實體中參數(shù)不一致的原因分析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
使用SpringBoot打jar包并部署到Tomcat詳細(xì)步驟
今天帶大家來學(xué)習(xí)怎么使用SpringBoot打jar包并部署到Tomcat,文中有非常詳細(xì)的步驟及代碼示例,對正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05

