String的不可變性
Java.lang包下面的的String在平時很常見,我們都知道String是不可變類。簡單說來,不可變類的實例是不能被修改的,每個實例中包含的信息都必須在該實例創建的時候就提供出來,並且在對象的整個生存週期內固定不變。
在String類中,是通過一個不可變的字符數組來存儲對象的字符序列的,final關鍵字使這個變量是不可變的,所以Sting對象的內容也是不可變的。
private final char value[];
很多初學者對於不可變類還是很不能理解,因爲通過下面的代碼,看起來改變了字符串對象s。
public static void main(String[] args) {
String s = "abc";
System.out.println(s);
s = "cde";
System.out.println(s);
}
輸出結果爲
abc
cde
其實這是因爲對String對象的創建和存儲機制不瞭解產生的誤解。下面我們來探討String的對象創建和初始化。
String的對象創建和初始化機制
字符串的聲明和初始化主要有以下兩種情況:
(1) 對於String s1=new String(“abc”)語句與String s2=new String(“abc”)語句,存在兩個引用對象s1、s2,兩個內容相同的字符串對象”abc”,它們在內存中的地址是不同的。只要用到new總會生成新的對象。
(2) 對於String s1 = “abc”語句與String s2 = “abc”語句,在JVM中存在着一個字符串池string pool,其中保存着很多String對象,並且可以被共享使用,s1、s2引用的是同一個常量池中的對象。當創建一個字符串常量的時候,例如String s = “abc”,會首先在字符串常量池中查找是否已經有相同的字符串被定義,它的判斷依據是String類equals(Object obj)方法的返回值,也就是判斷是否有相同的字符串序列。如果已經定義,則直接獲取對其的引用,此時不需要創建新的對象,如果沒有定義,則首先創建這個對象,然後把它加入到字符串池中,再將它的引用返回。
例如:
String s1=“abc”; //在常量區裏面存放了一個”abc”字符串對象
String s2=“abc”; //s2引用常量區中的對象,因此不會創建新的對象
String s3=new String(“abc”) //在堆中創建新的對象
String s4=new String(“abc”) //在堆中又創建一個新的對象
回到String對象的不可變特性
public static void main(String[] args) {
String s = "abc";
//在常量區中存放一個"abc"字符串對象,並把s指向這個對象
System.out.println(s);
s = "cde";
//在string pool中查找是否存在"dec",不存在則創建這個對象,並把s指向這個對象
System.out.println(s);
}
通過上面的分析,我們可以知道,改變的只是s的指向,而“abc”和“cde”這兩個字符串對象並沒有改變,內存中字符串對象如下所示:
執行String s = “abc”後s的指向
執行 s = “cde”後s的指向
改變的只是s的指向,而字符串對象的內容並沒有發生改變。
另外,在進行字符串的對象創建時我們還可能會使用“+”來進行字符串的連接, 如果用+號來實現String的串接時有兩種情況:
1)僅當+號兩邊均爲字符串常量時,纔將其+後的結果當做字符串常量,且該結果直接放入String Pool;
2)若+號兩邊有一方爲變量時,+後的結果即當做非字符串常量處理(等同於new String()的效果)。
改變不可變的String對象
如果我們真的需要去改變String對象,那麼應該怎麼做呢?
這個時候就需要用到java的反射機制了,我們可以通過反射機制去獲取String對象的value數組,並修改value字符數組的相應內容,這樣子字符串對象也會相應的發生改變。代碼如下:
public static void main(String[] args) {
try {
String s = "abc";
Field valuefield = String.class.getDeclaredField("value");
valuefield.setAccessible(true);
char[] value = (char[]) valuefield.get(s);
value[1] = '_';
System.out.println(s);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
輸出結果如下:
a_c