什麼是不可變對象 ?不可變對象指的是在建立一個對象之後 ,不能再改變它的狀態 ,那麼這個對象就是不可變的 。不能改變狀態的意思是 ,不能改變對象內的成員變數 ,包括基礎資料型別 (Elementary Data Type)的值不能改變 ,參考型別的變數不能指向其它的對象 ,參考型別指向的對象狀態也不能改變 。
這裡插播一下對象和對象的引用之間的區別 ,對象的引用是放在棧中的 ,而對象是放在堆中的 ,看這個例子 String s = "123" ; s = "456" ; 表面上 s 看是變了 ,但是要搞清楚 ,變的只是 String 對象的引用 s ,而 “123” 這個對象是沒有變化的 。看一個圖 :
我們都說 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[]; /** Cache the hash code for the string */ private int hash; // Default to 0 public String(String original) { this.value = original.value; this.hash = original.hash; } public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); } public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String) anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } ...
我們通過分析源碼可知 ,首先 String 類和存放字串的 char 型數組都是 final 類型的 ,保證了 String 類不能被繼承 ,value 這個引用本身不會該變 ,但是這樣還不能說明屬性是不可變的 ,因為我們可以通過改變 value 數組中具體的值來達到改變 value 的目的 。
public static void main(String[] args) { char[] value = {'a','b','c','d'}; System.out.println(value); for (int i = 0; i < value.length; i++) { if(i == 1){ value[i] = 'B'; } } System.out.println(value); //aBcd }
但是進一步研究會發現 ,對於 value 這個屬性 ,被定義為 private ,而且 String 並沒有提供相對應的 get set 方法 ,所以我們也不能操作它 。而那些我們常認為改變了 String 對象的方法 ,比方說 subString concat toUpperCase tirm 看了源碼之後發現 ,這些方法都是重新建立了一個 char 數組 ,並沒有改動之前的對象 。這也就是我們常說 String 是不可變對象的原因 。但是我們還是可以通過反射的方式來改變 value 的值 ,看個例子就好 ,但是我們一般不這麼做 。
public static void main(String[] args) throws Exception { //建立字串"Hello World", 並賦給引用s String s = "Hello World"; System.out.println("s = " + s); //Hello World //擷取String類中的value欄位 Field valueFieldOfString = String.class.getDeclaredField("value"); //改變value屬性的存取權限 valueFieldOfString.setAccessible(true); //擷取s對象上的value屬性的值 char[] value = (char[]) valueFieldOfString.get(s); //改變value所引用的數組中的第5個字元 value[5] = '_'; System.out.println("s = " + s); //Hello_World }
為什麼要將 String 設計為不可變對象呢 ?當然是為了提高效率和安全 ,也是由於 String 的超高使用頻率 。效率主要體現在當我們複製 String 對象的時候我只需要複製引用即可 ,不需要複製具體的對象 ,而在多線程環境中 ,若是不同的線程同時修改 String 對象 ,相互之間也不會有影響 。因為一旦改變都會建立一個新的字串 ,保證了線程的安全 。
另外在堆中有個字串常量池 ,我們建立的字串都會存在這裡 ,當建立相同的字串的時候 ,其實指向的是同一個地方 。這就節省了大量的空間 ,當然 ,能出現字串常量池也是因為 String 是不可變對象 。
因為 String 不可變 ,所以在建立對象的時候就已經將 hashCode 的值計算出來緩衝在欄位 hash 中 。這樣在 Map 中 String 就很適合作為主鍵 ,速度快 。(因為在散列表中我們定位時會計算主鍵的 hash 值 。)
/** Cache the hash code for the string */ private int hash; // Default to 0
還有在資料庫連接的時候我們通過字串來傳遞使用者名稱 ,密碼 ,串連的庫等資訊 ,若字串可變 ,則很可能被駭客篡改 。
PS. 基本類型對應的封裝類都是不可變對象 ,這樣設計的原因還是因為使用太頻繁 ,好處在上面已經說過了 。突然感覺學到了好多之前沒注意到的細節 。
源碼版本為 JDK 1.7
推薦閱讀:www.cnblogs.com/YJK923/p/9479805.html
參考資料:zhuanlan.zhihu.com/p/38144507