1. 程式人生 > 實用技巧 >String型別為什麼設計成不可變的?

String型別為什麼設計成不可變的?

想要完全瞭解String,在這裡我們需要解決以下幾個問題

(1)什麼是不可變物件?

(2)String如何被設計成不可變物件的?

(3)有什麼辦法能夠改變String?

(4)JAVA語言為什麼把String型別設計成不可變?

帶著這些問題就可以開始今天的文章了。

一、什麼是不可變物件

從字面意思也能夠理解,也就是我們的建立的物件不可改變。那什麼是不可變呢?為了實現建立的物件不可變,java語言要求我們需要遵守以下5條規則:

(1)類內部所有的欄位都是final修飾的。

(2)類內部所有的欄位都是私有的,也就是被private修飾。

(3)類不能夠被整合和拓展。

(4)類不能夠對外提供哪些能夠修改內部狀態的方法,setter方法也不行。

(5)類內部的欄位如果是引用,也就是說可以指向可變物件,那我們程式設計師不能獲取這個引用。

正是由於我們的String型別遵循了上面5條規則,所以才說String物件是不可變的。想要去了解他還是看看String型別內部長什麼樣子再來看上面5條規則吧。

二、String如何被設計成不可變物件的

1、疑惑一

在看之前,我們先給出一個疑惑問題,我們看下面的程式碼,

public class Test2 {
public static void main(String[] args) {
String a="張三";
System.out.println(a);
a="李四";
System.out.println(a);
}
}
//output:
//張三
//李四

在文章一開始我們就說了,String物件是不可變的,這裡a=張三,然後a=李四,這符合String的不可變性嘛?答案是當然符合。

從上面這張圖我們可以看到,在第一次String a="張三"的時候,在堆中建立了同一個物件“張三”。後來我們在執行a="李四"的時候再記憶體中又建立了一個物件“李四”。也就是說我們的a僅僅只是改變了引用a指向的地址而已。

2、原始碼解釋疑惑

既然a指向的引用地址改變了,那麼其String內部肯定有一個變數,能夠指向不同的實際物件,想要進一步弄清楚我們就進入其String的內部來看看。

我們在這裡主要通過String類的原始碼來分析,看一下Java語言是如何設計,能把String型別設計成不可變的。這裡給出的是jdk1.8的一部分原始碼。

publicfinalclassString
implementsjava.io.Serializable,Comparable<String>,CharSequence{
/**Thevalueisusedforcharacterstorage.*/
privatefinalcharvalue[];

/**Cachethehashcodeforthestring*/
privateinthash;//Defaultto0
......
}

上面最主要的是兩個欄位:value和hash。我們在這裡主要是看value陣列,hash和主題無關所以這裡不再講解了,我有專門的文章介紹hash。

我們的String物件其實在內部就是一個個字元然後儲存在這個value數組裡面的。但是value對外沒有setValue的方法,所以整個String物件在外部看起來就是不可變的。我們畫一張圖解釋一下上面的疑惑

現在明白了吧,也就是說真正改變引用的是value,因為value也是一個數組引用。這也可以很方便的解釋下一個疑惑問題了。

3、疑惑二

既然我們的String是不可變的,好像內部還有很多substring, replace, replaceAll這些操作的方法。好像都是對String物件改變了,解釋起來也很簡單,我們每次的replace這些操作,其實就是在堆記憶體中建立了一個新的物件。然後我們的value指向不同的物件罷了。

面試的時候我們只是解釋上面的原因其實不是那麼盡善盡美,想要更好的去加薪去裝逼,我們還需更進一步回答。

三、有什麼辦法能夠改變String

既然有這個標題。那肯定就是有辦法的,別忘了我們的反射機制,在通常情況下,他可以做出一些違反語言設計原則的事情。這也是一個技巧,每當面試官問一些違反語言設計原則的問題,你就可以拿反射來反駁他。下面我們來看一下:

public class Test2 {
public static void main(String[] args) {
String str = "張三";
System.out.println(str);
try {
//我們通過反射獲取內部的value字元陣列
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value;
value = (char[]) field.get(str);
//把字串第一個字元變成王
value[0] = '王';
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//output:
//張三
//王三

我們可以通過反射來改變String。現在我們知道它的原理以及用法,也知道可以通過反射來改變String,還有一個問題我們沒有弄清楚,面試的時候你也可以反問他,來進一步提升自己的逼格。

四、JAVA語言為什麼把String型別設計成不可變

這裡有幾個特點。

第一:在Java程式中String型別是使用最多的,這就牽扯到大量的增刪改查,每次增刪改差之前其實jvm需要檢查一下這個String物件的安全性,就是通過hashcode,當設計成不可變物件時候,就保證了每次增刪改查的hashcode的唯一性,也就可以放心的操作。

第二:網路連線地址URL,檔案路徑path通常情況下都是以String型別儲存, 假若String不是固定不變的,將會引起各種安全隱患。就好比我們的密碼不能以String的型別儲存,,如果你將密碼以明文的形式儲存成字串,那麼它將一直留在記憶體中,直到垃圾收集器把它清除。而由於字串被放在字串緩衝池中以方便重複使用,所以它就可能在記憶體中被保留很長時間,而這將導致安全隱患

第三:字串值是被保留在常量池中的,也就是說假若字串物件允許改變,那麼將會導致各種邏輯錯誤

本文來自轉載