String類是不可變類,一個String對象被創(chuàng)建以后,包含這個對象中的字符串序列是不可改變的。與其問String為什么是不可變的,還不如問String類是如何實現(xiàn)其對象不可變的。
什么是不可變對象?
如果一個對象它被創(chuàng)建后,狀態(tài)不能改變,則這個對象被認(rèn)為是不可變的。
String是如何實現(xiàn)其對象不可變?
首先需要補(bǔ)充一個容易混淆的知識點:當(dāng)使用final修飾基本類型變量時,不能對基本類型變量重新賦值,因此基本類型變量不能被改變。但對于引用類型變量而言,它保存的僅僅是一個引用,final只保證這個引用變量所引用的地址不會改變,即一直引用同一個對象,但這個對象完全可以發(fā)生改變。例如某個指向數(shù)組的final引用,它必須從此至終指向初始化時指向的數(shù)組,但是這個數(shù)組的內(nèi)容完全可以改變。
我們來看一下String類的兩個主要成員變量,其中value指向的是一個字符串?dāng)?shù)組,字符串中的字符就是用這個value變量存儲起來的,并且用final修飾,也就是說value一旦賦予初始值之后,value指向的地址就不能再改變了。雖然value指向的數(shù)組是可以改變的,但是String也沒有提供相應(yīng)的方法讓我們?nèi)バ薷膙alue指向的數(shù)組的元素。然而在StringBuilder中是提供了響應(yīng)的方法讓我們?nèi)バ薷膙alue指向的數(shù)組的元素,這也是StringBuilder的字符串序列可變的原因。
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0
有一些看起來String對象可變的幻覺?
String中提供了一些看似可以改變String對象的方法,但實際上它們已經(jīng)是指向了一個新建的對象。
程序例子:
public static void main(String[] args) { System.out.println("str1的內(nèi)存地址:" + System.identityHashCode(str1)); // str1的內(nèi)存地址已經(jīng)改變了 System.out.println("執(zhí)行+=后str1的內(nèi)存地址:" + System.identityHashCode(str1)); System.out.println("拼接之后str1的值:" + str1); // 創(chuàng)建一個新的對象來保存拼接之后的值 String str4 = str3.concat("456"); System.out.println("str3的值:" + str3); System.out.println("str4的值:" + str4); String str6 = str5.replace("A", "B"); System.out.println("str5的值:" + str5); System.out.println("str6的值:" + str6);
運(yùn)行結(jié)果:
str1的內(nèi)存地址:1922154895 執(zhí)行+=后str1的內(nèi)存地址:883049899
程序分析:
str1+=str2實際上是執(zhí)行了str1=(new StringBuilder()).append(str2).toString();前后實際額外產(chǎn)生了一個StringBuilder與一個helloworld的字符串常量。str1執(zhí)行+=前后內(nèi)存的示意圖如下所示:
上面使用了String類的concat與replace方法,執(zhí)行這兩個操作不會對原來的對象產(chǎn)生影響,他們會返回一個全新的對象。我們可以來看一下這兩個方法的源碼。
concat方法源碼:
public String concat(String str) { int otherLen = str.length(); char buf[] = Arrays.copyOf(value, len + otherLen); return new String(buf, true);
replace方法源碼:
public String replace(char oldChar, char newChar) { if (oldChar != newChar) { char[] val = value; /* avoid getfield opcode */ char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[i] = (c == oldChar) ? newChar : c; return new String(buf, true);
String對象真的不可變嗎?
雖然value是final修飾的,只是說明value不能再重新指向其他的引用。但是value指向的數(shù)組可以改變,一般情況下我們是沒有辦法訪問到這個value指向的數(shù)組的元素。But,反射,對,反射可以,牛逼吧。可以反射出String對象中的value屬性, 進(jìn)而改變通過獲得的value引用改變數(shù)組的結(jié)構(gòu)。show you the code!
public static void main(String[] args) throws Exception { String str = "Hello World"; System.out.println("修改前的str:" + str); System.out.println("修改前的str的內(nèi)存地址" + System.identityHashCode(str)); Field valueField = String.class.getDeclaredField("value"); valueField.setAccessible(true); char[] value = (char[]) valueField.get(str); // 改變value所引用的數(shù)組中的字符 System.out.println("修改后的str:" + str); System.out.println("修改前的str的內(nèi)存地址" + System.identityHashCode(str));
運(yùn)行結(jié)果:
修改前的str的內(nèi)存地址1922154895 修改前的str的內(nèi)存地址1922154895
可以看到str的字符串序列已經(jīng)被改變了,但是str的內(nèi)存地址還是沒有改變。有疑問?在下方留言哦。
|