1. 程式人生 > 其它 >JAVA String類為什麼是Final不可變的

JAVA String類為什麼是Final不可變的

技術標籤:javastring堆疊

為什麼呢~為什麼呢~~

原因:

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”)是儲存在中,所以地址不一樣。

好了今天就講到這裡,本人水平一般,能力有限,如果有不足的地方,希望大家多多指點,我們一起進步!

本文也參考了文章:https://www.jianshu.com/p/9c7f5daac283