您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)深入淺析java項(xiàng)目中String的可變性,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
起因
正如我們所理解的,通過(guò)
String hello = "Hello World!";
和
String xx = new String("Hello World!");
得到的字符串對(duì)象是不一樣的,new方式是在堆空間中創(chuàng)建的,而直接的字符串則是先被放到常量池中。如果有新的與之一樣的對(duì)象被創(chuàng)建,則直接讓這個(gè)新對(duì)象引用常量池中的這個(gè)地址即可。
這樣的好處就是可以最大限度的節(jié)省內(nèi)存空間。
而使用new方式創(chuàng)建的則就不一樣了,只要是用了new創(chuàng)建字符串,就會(huì)在堆空間中開(kāi)辟出一塊內(nèi)存,然后返回這個(gè)內(nèi)存地址的引用。所以這樣創(chuàng)建的對(duì)象,即使內(nèi)容一致,也不會(huì)是指向同一個(gè)內(nèi)存地址。
下面用幾個(gè)簡(jiǎn)單的代碼做下測(cè)試。
/** *字符串中對(duì)于內(nèi)容和地址的判定可以用下面兩種方式,但側(cè)重點(diǎn)不一樣。 */ equals // 判斷 兩個(gè)字符串的內(nèi)容是否一致 == // 判斷兩個(gè)字符串的內(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!"); // 下面開(kāi)始比較引用和內(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é)果卻是和我們所說(shuō)的那樣。
深入源碼
不出所料,String確實(shí)是“不可變的”,每次改變底層其實(shí)都是創(chuàng)建了一個(gè)心的字符串對(duì)象,然后賦予了新值。
為什么會(huì)這樣呢?我們也許可以在源碼中找到真相。
哦,原來(lái)Java對(duì)于String類(lèi)只是維護(hù)了一個(gè)final類(lèi)型的字符數(shù)組啊。怪不得賦值之后就不能改變了呢。
但是也許你會(huì)有疑問(wèn),咦,不對(duì)啊,“我經(jīng)常使用String的什么replace方法改變字符串的內(nèi)容啊。你這則么解釋呢?”
其實(shí)答案還是那樣,它真的沒(méi)變,我們并沒(mé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)用者新對(duì)象了。
真的不可變嗎?
讀到上面的內(nèi)容,其實(shí)基本上已經(jīng)夠了。但是了解一下更深層次的內(nèi)容,相信對(duì)我們以后編程來(lái)說(shuō)會(huì)更好。
源碼中清楚的使用char[] value來(lái)盛裝外界的字符串?dāng)?shù)據(jù)。也就是說(shuō)字符串對(duì)象的不可變的特性,其實(shí)是源自value數(shù)組的final特性。
那么我們可以這么想,我們不改變String的內(nèi)容,而是轉(zhuǎn)過(guò)頭來(lái)改變value數(shù)組的內(nèi)容(可以通過(guò)反射的方式來(lái)修改String對(duì)象中的private屬性的value),結(jié)果會(huì)怎樣呢?
答案是真的會(huì)變哦。
可以先看下下面的代碼
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ù)組的真實(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)過(guò)反射獲取到這三個(gè)字符串對(duì)象的最底層的引用數(shù)組value,發(fā)現(xiàn)如果一開(kāi)始內(nèi)容一致的話,java底層會(huì)將創(chuàng)建的字符串對(duì)象指向同一個(gè)字符數(shù)組 * */ // 通過(guò)反射修改字符串引用的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); // 驗(yàn)證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)存中的同一個(gè)value字符數(shù)組。這也說(shuō)明了Java在底層做了足夠強(qiáng)的優(yōu)化處理。
當(dāng)創(chuàng)建了一個(gè)字符串對(duì)象時(shí),底層會(huì)對(duì)應(yīng)一個(gè)盛裝了相應(yīng)內(nèi)容的字符數(shù)組;此時(shí)如果又來(lái)了一個(gè)同樣的字符串,對(duì)于value數(shù)組直接獲取剛才的那個(gè)引用即可。(相信我們都知道,在Java中數(shù)組其實(shí)也是一個(gè)對(duì)象類(lèi)型的數(shù)據(jù),這樣既不難理解了)。
不管是字符串直接引用方式,還是new一個(gè)新的字符串的方式,結(jié)果都是一樣的。它們內(nèi)部的字符數(shù)組都會(huì)指向內(nèi)存中同一個(gè)“對(duì)象”(value字符數(shù)組)。
總結(jié)
稍微有點(diǎn)亂,但是從這點(diǎn)我們也可以看出String的不可變性其實(shí)仍舊是對(duì)外界而言的。在最底層,Java把這一切都給透明化了。我們只需要知道String對(duì)象有這點(diǎn)特性,就夠了。
其他的,日常應(yīng)用來(lái)說(shuō),還是按照String對(duì)象不可變來(lái)使用即可。
上述就是小編為大家分享的深入淺析java項(xiàng)目中String的可變性了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。