Java中的String为什么是不可变的?
? ? ? ?String類是不可變類,一個String對象被創建以后,包含這個對象中的字符串序列是不可改變的。與其問String為什么是不可變的,還不如問String類是如何實現其對象不可變的。
什么是不可變對象?
? ? ? ?如果一個對象它被創建后,狀態不能改變,則這個對象被認為是不可變的。
String是如何實現其對象不可變?
? ? ? ?首先需要補充一個容易混淆的知識點:當使用final修飾基本類型變量時,不能對基本類型變量重新賦值,因此基本類型變量不能被改變。但對于引用類型變量而言,它保存的僅僅是一個引用,final只保證這個引用變量所引用的地址不會改變,即一直引用同一個對象,但這個對象完全可以發生改變。例如某個指向數組的final引用,它必須從此至終指向初始化時指向的數組,但是這個數組的內容完全可以改變。
? ? ? ? 我們來看一下String類的兩個主要成員變量,其中value指向的是一個字符串數組,字符串中的字符就是用這個value變量存儲起來的,并且用final修飾,也就是說value一旦賦予初始值之后,value指向的地址就不能再改變了。雖然value指向的數組是可以改變的,但是String也沒有提供相應的方法讓我們去修改value指向的數組的元素。然而在StringBuilder中是提供了響應的方法讓我們去修改value指向的數組的元素,這也是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對象的方法,但實際上它們已經是指向了一個新建的對象。
程序例子:
public static void main(String[] args) {String str1 = "hello";// 打印str1的內存地址System.out.println("str1的內存地址:" + System.identityHashCode(str1));String str2 = "world";str1 += str2;// str1的內存地址已經改變了System.out.println("執行+=后str1的內存地址:" + System.identityHashCode(str1));System.out.println("拼接之后str1的值:" + str1);String str3 = "123";// 創建一個新的對象來保存拼接之后的值String str4 = str3.concat("456");// concat操作不會改變原來str3的值System.out.println("str3的值:" + str3);System.out.println("str4的值:" + str4);String str5 = "ABC";// replace操作不會改變原來str6的值String str6 = str5.replace("A", "B");System.out.println("str5的值:" + str5);System.out.println("str6的值:" + str6);}運行結果:
str1的內存地址:1922154895 執行+=后str1的內存地址:883049899 拼接之后str1的值:helloworld str3的值:123 str4的值:123456 str5的值:ABC str6的值:BBC程序分析:
? ? ? ?str1+=str2實際上是執行了str1=(new StringBuilder()).append(str2).toString();前后實際額外產生了一個StringBuilder與一個helloworld的字符串常量。str1執行+=前后內存的示意圖如下所示:
? ? ? ?上面使用了String類的concat與replace方法,執行這兩個操作不會對原來的對象產生影響,他們會返回一個全新的對象。我們可以來看一下這兩個方法的源碼。
concat方法源碼:
public String concat(String str) {int otherLen = str.length();if (otherLen == 0) {return this;}int len = value.length;char buf[] = Arrays.copyOf(value, len + otherLen);str.getChars(buf, len);return new String(buf, true);}replace方法源碼:
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;}String對象真的不可變嗎?
? ? ? ?雖然value是final修飾的,只是說明value不能再重新指向其他的引用。但是value指向的數組可以改變,一般情況下我們是沒有辦法訪問到這個value指向的數組的元素。But,反射,對,反射可以,牛逼吧。可以反射出String對象中的value屬性, 進而改變通過獲得的value引用改變數組的結構。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的內存地址" + System.identityHashCode(str));// 獲取String類中的value字段Field valueField = String.class.getDeclaredField("value");// 改變value屬性的訪問權限valueField.setAccessible(true);// 獲取str對象上value屬性的值char[] value = (char[]) valueField.get(str);// 改變value所引用的數組中的字符value[3] = '?';System.out.println("修改后的str:" + str);System.out.println("修改前的str的內存地址" + System.identityHashCode(str));}運行結果:
修改前的str:Hello World 修改前的str的內存地址1922154895 修改后的str:Hel?o World 修改前的str的內存地址1922154895? ? ? ?可以看到str的字符串序列已經被改變了,但是str的內存地址還是沒有改變。有疑問?在下方留言哦。
總結
以上是生活随笔為你收集整理的Java中的String为什么是不可变的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过Python终端输入命令对NAO机器
- 下一篇: 二叉树探究之非叶子结点和叶子结点对半分且