Java中的String對象是不可變的嗎

有個仁兄在 StackOverflow 上發起了一個問題,是這麼問的: html

衆所周知Java中的String對象是不可變的,但咱們來看下面這段代碼: java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
String s1 ="Hello World";
String s2 ="Hello World";
String s3 = s1.substring(6);
System.out.println(s1);// Hello World
System.out.println(s2);// Hello World
System.out.println(s3);// World
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] ='J';
value[7] ='a';
value[8] ='v';
value[9] ='a';
value[10] ='!';
System.out.println(s1);// Hello Java!
System.out.println(s2);// Hello Java!
System.out.println(s3);// World

爲何這段代碼會是這樣的運行結果?爲何s1和s2的值被改變了,可是s3的值卻沒有? 數組

回答#1: 工具

String對象是不可變的,但這僅意味着你沒法經過調用它的公有方法來改變它的值。 spa

上面的代碼經過反射機制繞過了正常的API。採用這種方式,你還能夠改變枚舉的值,甚至能夠改變Integer類型自動裝箱時使用的查找表。 指針

在這裏,因爲s1和s2指向同一個內部的字符串對象,所以他們的值都被改變了。正如其餘回答所述,這是由編譯器實現的。 htm

s3沒有被改變的緣由確實令我詫異,我過去一直認爲s3和s1共享同一個value數組(在Java 7u6以前的版本中的確是這樣)。然而,經過查看String類的源碼,咱們能夠看出子串對象的value數組是從原字符串對象中拷貝獲得的(經過使用Arrays.copyOfRange(..)方法)。這是s3沒有被改變的緣由。 對象

你能夠安裝一個SecurityManager來防止惡意代碼執行這一類的操做。但須要注意的是,有些庫的實現依賴於這種反射技巧(例如ORM工具,AOP庫等)。 ci

我在回覆的開頭寫道String對象並非真的不可變,只是「看上去不可變」。這可能會誤導讀者覺得String類的當前版本在訪問限制方面有所疏忽,但事實上value數組使用了private和final修飾符。所以開發者須要注意:在Java中沒法將數組聲明爲不可變的,即便使用了正確的訪問修飾符,也不能將它暴露在類的外面。 開發

鑑於這個話題如此火熱,向各位推薦一些進階讀物:2009年JavaZone會議上Heinz Kabutz發表的關於反射技術的瘋狂演說,本文涵蓋了反射操做中的常見問題,以及其餘一些有關反射技術的內容。本文很是好,也很是瘋狂。

本文揭示了爲何反射技術在某些場景下很是實用,但在大多數狀況下,你應該避免使用它。

回答#2:

在Java中,若是兩個String類型的變量被初始化爲同一個字符串,那麼這兩個變量將被賦值以相同的對象引用。這就是表達式「Test1==Test2」返回值爲true的緣由。

1
2
3
StringTest1="Hello World";
StringTest2="Hello World";
System.out.println(test1==test2);// true

Test3是由substring()方法建立的一個新的String對象,它並無和Test1共享同一個value數組。(注:因爲原做者筆誤,下圖中變量test1和test3的首字母並未大寫,望讀者注意)

咱們能夠經過反射技術訪問String對象,獲取到value數組的指針:

1
2
Field field =String.class.getDeclaredField("value");
field.setAccessible(true);

改變這個value數組的值便可以改變全部持有該數組指針的String對象的值,所以Test1和Test2的值都發生了改變。但因爲Test3是由substring()方法建立的一個新的String對象,它的值並無被改變。

相關文章
相關標籤/搜索