前幾天一個面試被問到String為什么是不可變的?
, 自我感覺當時回答的不太理想, 事后總結一下
不可變的是什么
我們談論的String不可變, 指的是字符串的值不可變
例: String s = "hello"
s的值就是hello
, 不可變也指的是這個值不可變
類比到int基本類型就相當於int i = 1
, 假如這里i的值不可變, 那指的就是1不可變
為什么不可變
眾所周知Java的String類型並非基本類型, 即String是一個類
既然String是類, 那我們就深入其內部實現來一探究竟
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
//......
}
從源碼來看, String類內部是用char數組來保存字符串的值, 並且char[]是final的, 這里的final意味着什么呢?
- value必須在構造時為其賦值
- 賦值后value的引用不能再變
當我們實例化一個String對象並得到其引用后, 構造已經結束了, 即value的引用已經不能再變了
那么value的值呢, 理論上是可以改變的, 只要我們拿到value的引用, 可以直接通過下標改變他的值
實際上呢?
value的值我們從String外部獲取不到
- 首先, 構造的時候我們傳入String的值, String內部賦值給value字段的時候都經過copy, 也就是說我們傳給String的值經過構造后已經有了一份我們獲取不到的備份留在了String內部, 我們改變原來的值對String內部的value已經毫無影響
char[] c = new char[32];
c[0] = 'h';
String s = new String(c);
System.out.println(s);
c[1] = 'e';
System.out.println(s);
毫無疑問, 兩次的輸出都是h
-
其次, String類沒有提供對外的接口來改變value的值, 通過查看String類源碼可以看到, String類所有的公開方法中, 沒有一個可以修改value的值
-
最后
String s = "hello";
s = "world";
這種情況, s的值貌似改變了, 從hello
變成了world
其實這里s所改變的是他所引用的對象, 並不是String對象的值改變了
怎么說呢? 我們這樣String s = "hello"
寫代碼只是一種簡寫, 或者稱為'語法糖'
實際執行的時候是什么樣子的呢? 非要用Java代碼表示的話大致意思是這樣String s = String.valueOf("hello")
嗯? 這樣表述貌似也有些問題... 改天再單獨總結...
這里意思就是valueOf("hello")
會返回一個內部的value字段存的是"hello"的String對象
從上面幾點分析我們知道, 拿到String對象的時候, 內部的value字段已經無法修改了
那么這里的s = "world"
, 這個賦值又是什么意思呢?
根據上面對valueOf
的分析, 這里的s = "world"
賦值之后s是一個內部字段存的是"world"的String對象
又因為value的值在String構造的時候就已經指定且不可再變, 所以這個s和之前的s引用的必然不是同一個對象
綜上, 對s賦值是改變了s所引用的對象, 而改變前后兩個String對象既不是同一個對象, 內部的value值又不一樣
所以, 直接賦值也不能改變字符串的值, 改變的只是引用
所以, String不可變.
真的不可變嗎?
按照上述的分析, 貌似真的不可變
因為一般情況下我們獲取不到String內部的value數組的引用
那么二般情況呢
char[] origin = new char[32];
origin[0] = 'h';
String s = new String(origin);
System.out.println(s);
try {
Field f = s.getClass().getDeclaredField("value");
f.setAccessible(true);
Object o = f.get(s);
if(o instanceof char[]) {
char[] c = (char[]) o;
System.out.println(c.length);
c[1] = 'e';
c[2] = 'l';
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
}
利用反射我們可以直接獲取類內部的屬性, 掙脫了訪問權限的束縛
獲取了String內部的value數組, 改變了String的值
假如以后遇到別的改變String值的方法, 再來記錄