1. 前言
最近看到幾個有趣的關于Java核心類String的問題。
String類是如何實現其不可變的特性的,設計成不可變的好處在哪里。
為什么不推薦使用+號的方式去形成新的字符串,推薦使用StringBuilder或者StringBuffer呢。
2. String類是如何實現不可變的
String類的一大特點,就是使用Final類修飾符。
A class can be declared final if its definition is complete and no subclasses are desired or required.
Because a final class never has any subclasses, the methods of a final class are never overridden .
Java SE 7 官方手冊中的定義如上,如果你認為這個類已經定義完全并且不需要任何子類的話,可以將這個類聲明為Final,Final類中的方法將永遠不會被重寫。
在Java中,String是被設計成一個不可變(immutable)類,一旦創建完后,字符串本身是無法通過正常手段被修改的。
選了substring方法來做一個代表,其他常見的涉及String操作的方法都是類似,如果你操作后的內容會和目前String中的內容不一致的話,那么都是重新創建一個新的String類返還,不會讓你去修改內部的內容。
將String類設計成Final類,能夠避免其方法被子類重寫,從而破壞了它本身方法的實現,進而破壞了不可變的特性。
2.1 String類設計成不可變的好處
我們都不是Java語言的設計者,不知道其為何一定要設計成不可變,試著做一些猜想。
1.可以實現多個變量引用JVM內存中的同一個字符串實例。見后文String Pool的介紹。
2.安全性,String類的用途實在太廣了,如果可以隨意修改的,是不是很恐怖。
3.性能,String大量運用在哈希的處理中,由于String的不可變性,可以只計算一次哈希值,然后緩存在內部,后續直接取就好了。如果String類是可變的話,在進行哈希處理的時候,需要進行大量的哈希值的重新計算。
2.2 String Pool
上文說了,設計成不可變后,可以多個變量引用JVM上同一塊地址,可以節省內存空間,相同的字符串不用重復占用Heap區域空間。
String test1 ="abc";
String test2 ="abc";
通常我們平時在使用字符串是,都是通過這種方式使用,那么JVM中的大致存儲就是如下圖所示。
兩個變量同時引用了String Pool中的abc,如果String類是可變的話,也就不能存在String Pool這樣的設計了。 在平時我們還會通過new關鍵字來生成String,那么新創建的String是否也會和上文中的示例一樣共享同一個字符串地址呢。
String test1 ="abc";
String test2 ="abc";
String test3 =newString("abc");
答案是不會,使用new關鍵字會在堆區在創建出一個字符串,所以使用new來創建字符串還是很浪費內存的,內存結構如下圖所示。
2.3 不推薦使用+來拼裝字符串的原因。
首先我們來看這一段代碼,應該是之前寫代碼比較常見的。
String test1 ="abc";
String test2 ="abc";
String test3 = test1 + test2;
test3通過test1和test2拼接而成,我們看一下這個過程中的字節碼。
從以上圖我們可以看到,目前的JDK7的做法是,會通過新建StringBuilder的方式來完成這個+號的操作。這是目前的一個底層字節碼的實現,那么是不是沒有使用StringBuilder或者StringBuffer的必要了呢。還是有的,看下一個例子。
String test2 ="abc";
String test3 ="abc";
for(inti =0; i <5; i++) {
test3 += test2;
}
在上述代碼中,我們還是使用+號進行拼接,但這次我們加了一個循環,看一下字節碼有什么變化。
每次循環都會創建一個StringBuilder,在末尾再調用toString返還回去,效率很低。繼續看下一個例子,我們直接使用StringBuilder,來做拼接。
String test2 ="abc";
// 使用StringBuilder進行拼接
StringBuilder test4 =newStringBuilder("abc");
for(inti =0; i <5; i++) {
test4.append(test2);
}
每次循環體中只會調用之前創建的StringBuilder的append方法進行拼接,效率大大提高。
3. 總結
本文主要探討了String類設計為Final修飾和不可變類的原因,以及為何在日常工作中不推薦使用+號進行字符串拼接。