深入String类不可变特性
String不可變的分析
- 一.從代碼層面分析
- 二.從內存結構角度分析
- 三.可以通過反射來修改char數組的值
- 四.一個例題
一.從代碼層面分析
二.從內存結構角度分析
三.可以通過反射來修改char數組的值
四.一個例題
一.從代碼層面分析:
string源碼中真正存儲元素的容器是final修飾的char數組,而且沒有提供set方法來提供對它的修改,這是首先在代碼層面上保證了不可變性。
而如StringBuilder和StringBuffer的char數組是提供了set方法,可以修改的。(真正的容器char數組是放在二者的父類AbstractStringBuilder中,而且它們的大部分方法都是使用的AbstractStringBuilder類的方法。)
String類中還有substring, replace, replaceAll, toLowerCase等方法可以獲取改變后的字符串,它不是在源字符串的基礎上改變的,而是在方法內部創建了一個新的String對象,賦值給引用。
例: 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引用指向的對象的內容(對象內存地址所存的內容)是不能改變的,但String引用(變量)是可以改變的,可以讓其指向另外一個字符串。(不能修改字符串的內容,但可以修改字符串的引用)
例: String s1 = "hello"; s1 = "world";這個代碼中,開始s1指向的是堆中的一塊內存地址內容是“hello”,執行到第二句代碼時,s1引用重新指向了堆中新創建的另一塊內存地址,內容是“world”。
所以String引用只要重新指向其他的字符串,那么除非新指向的這個字符串已經在字符串常量池中存在了,否則就會在堆中新創建一塊內存空間。(jdk1.8中字符串常量池也是在堆中)
三.可以通過反射來修改char數組的值
那么用什么方式可以訪問私有成員呢? 沒錯,用反射, 可以反射出String對象中的value屬性, 進而改變通過獲得的value引用改變數組的結構。下面是實例代碼:
String s1 = "a bc"; System.out.println(s1);// a bc獲取String類中的value字段 Field field = String.class.getDeclaredField("value");//改變value屬性的訪問權限 field.setAccessible(true);//獲取s1對象上的value屬性的值 char[] value = (char[]) field.get(s1); //修改value中的值 value[1] = '_'; System.out.println(s1);// a_bc在這個過程中,s始終引用的同一個String對象,但是在反射前后,這個String對象發生了變化, 也就是說,通過反射是可以修改所謂的“不可變”對象的。但是一般我們不這么做。
四.一個例題
指出下列程序運行的結果:
public class Example{String str=new String("tarena");char[]ch={'a','b','c'};public static void main(String args[]){Example ex=new Example();ex.change(ex.str,ex.ch);System.out.print(ex.str+" and ");System.out.print(ex.ch);}public void change(String str,char ch[]){//引用類型變量,傳遞的是地址,屬于引用傳遞。str="test ok";ch[0]='g';} }結果是:tarena and gbc
分析:
因為是引用類型變量,所以傳遞的是地址,屬于引用傳遞。即實參和形參開始是指向同一個內存地址的。
從前面對String類不可變特性的分析可知,這個題把str引用傳遞時,它在change方法內重新指向了一個新創建的內存地址。所以執行到這里時str引用和之前的已經不一樣了,但這個因為是在方法內部的屬于局部引用,會隨著方法結束而消失,所以當這個方法執行完后,str又變成之前指向"tarena"字符串的引用。
輸出的str不變也是由于String的不可變特性,這個char數組沒有這個特性,它的實參和形參一直是指向同一個內存地址的,所以它會改變。
上述例子對StringBuilder的測試結果如下:
public class Example{StringBuilder str = new StringBuilder("tarena");char[]ch={'a','b','c'};public static void main(String args[]){Example ex=new Example();ex.change(ex.str,ex.ch);System.out.print(ex.str+" and ");System.out.print(ex.ch);}public void change(StringBuilder str,char ch[]){// StringBuilder沒有str = "a";這種賦值操作//str = new StringBuilder("hello");// 不變,因為是new新創建的,在堆中是新建一個內存空間// 結果會改變,因為使用append是在原來的StringBuilder對象上改變的str.append("_hello");ch[0]='g';} }參考博客如下:
總結
以上是生活随笔為你收集整理的深入String类不可变特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 天高任鸟飞,在你还苦闷Android出路
- 下一篇: utrack调试 艾肯icon_艾肯(i