JAVA String類為什麼是Final不可變的
為什麼呢~為什麼呢~~
原因:
1、為了執行緒安全
2、因為要實現字串池
什麼是字串池:java中的字串池是儲存在Java堆記憶體中的字串池
字串池就像一個公共的大相簿,每一個字串就是一張照片,當你需要哪張照片的時候,發現相簿裡有這張照片,就可以直接拿過來用。如果相簿中沒有你想要的照片,你可以自己拍一張,然後把照片放到相簿中,自己和其他人都可以拿來用了。
3、為了實現String可以建立HashCode不可變性
正文開始
我們先來看一下String的原始碼(萬物皆可看原始碼~~)
可以看到,String是被final修飾的類,那final是幹什麼的呢?
- 首先你要理解final的用途,在分析String為什麼要用final修飾,final可以修飾類,方法和變數,並且被修飾的類或方法,被final修飾的類不能被繼承,即它不能擁有自己的子類,被final修飾的方法不能被重寫, final修飾的變數,無論是類屬性、物件屬性、形參還是區域性變數,都需要進行初始化操作。
- 在瞭解final的用途後,再看String為什麼要被final修飾:主要是為了”安全性“和”效率“的緣故
final修飾的String,代表了String的不可繼承性,final修飾的char[]代表了被儲存的資料不可更改性。但是:雖然final代表了不可變,但僅僅是引用地址不可變,並不代表了陣列本身不會變,請看下面圖片
可能你會有這個疑問,final修飾的不是不可以改變嗎!為什麼用final修飾了一個數組final int[] array = {1, 2, 3, 4, 5}; 還可以用修改資料?
再舉個例子~:
//例項1 public static void main(String... args) { final List<String> stringList = new ArrayList(); stringList.add("a"); stringList.add("b"); System.out.println(stringList.toString()); } //例項2 public static void main(String... args) { final List<String> stringList = new ArrayList(); stringList = new ArrayList<>(); }
- 在例項1中,我們用final修飾了一個集合‘stringList’,並對集合進行add()操作,執行成功。
- 在例項2中,我們對集合進行變更,執行失敗。
原因:final修飾的集合‘stringList’是一個引用,而這個引用指向了‘stringList’,在往集合裡新增資料的時候,並沒有影響到‘stringList’引用地址。而當我們 stringList = new ArrayList<>(); 為什麼就不可以了呢? 因為這就相當於修改引用地址,是不可以的。final的意思是地址不能改,但是地址指向的內容當然可以改。
我們看一下,在String的原始碼中是這樣定義的:
private final char value[];
陣列是私有方法,所以起作用的還有private,正是因為兩者保證了String的不可變性。
那麼為什麼保證String不可變呢,因為只有當字串是不可變的,字串池才有可能實現(跟我們文章開頭舉的例子一樣,可以理解為一個快取區,如果字串可變,如果在其它地方也有引用,指向的引用發生了變更,會發生混亂)。字串池的實現可以在執行時節約很多heap空間,因為不同的字串變數都指向池中的同一個字串。但如果字串是可變的,那麼String interning將不能實現,因為這樣的話,如果變數改變了它的值,那麼其它指向這個值的變數的值也會一起改變。
HashCode不可變性
因為字串是不可變的,所以在它建立的時候HashCode就被快取了,不需要重新計算。這就使得字串很適合作為Map中的鍵,字串的處理速度要快過其它的鍵物件。這就是HashMap中的鍵往往都使用字串。
我們來實際看一下,是不是一樣的
public static void main(String[] args) {
String a = "a";
String b = "a";
String c = new String("a");
String d = new String("a");
// 輸出Hashcode
System.out.println("a-hc :" + a.hashCode());
System.out.println("b-hc :" + b.hashCode());
System.out.println("c-hc :" + c.hashCode());
System.out.println("d-hc :" + d.hashCode());
// 輸出引用地址
printAddresses("a", a);
printAddresses("b", b);
printAddresses("c", c);
printAddresses("d", d);
}
輸出結果
a-hc :97
b-hc :97
c-hc :97
d-hc :97
a: 7aada3af8
b: 7aada3af8
c: 7aada3b28
d: 7aada3b40
我們看到,得出來的HashCode是一樣的,但是為什麼得出來的地址不一樣?
因為直接定義的String m = "a"; 是儲存在常量儲存區中的字串常量池中;而new String(“a”)是儲存在堆中,所以地址不一樣。
好了今天就講到這裡,本人水平一般,能力有限,如果有不足的地方,希望大家多多指點,我們一起進步!