String為什么是不可變的?


前幾天一個面試被問到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值的方法, 再來記錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM