凱倫說,公衆號ID: KailunTalk,努力寫出最優質的技術文章,歡迎關注探討。java
最近看到幾個有趣的關於Java核心類String的問題。數組
翻閱了網上的一些博客和stackoverflow,結合本身的理解作一個彙總。緩存
String類的一大特色,就是使用Final類修飾符。安全
A class can be declared final if its definition is complete and no subclasses are desired or required.app
Because a final class never has any subclasses, the methods of a final class are never overridden .性能
Java SE 7 官方手冊中的定義如上,若是你認爲這個類已經定義徹底而且不須要任何子類的話,能夠將這個類聲明爲Final,Final類中的方法將永遠不會被重寫。ui
在Java中,String是被設計成一個不可變(immutable)類,一旦建立完後,字符串自己是沒法經過正常手段被修改的。this
private final char value[]; // 一旦初始化後,引用不能被修改 public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }
選了substring方法來作一個表明,其餘常見的涉及String操做的方法都是相似,若是你操做後的內容會和目前String中的內容不一致的話,那麼都是從新建立一個新的String類返還,不會讓你去修改內部的內容。spa
將String類設計成Final類,可以避免其方法被子類重寫,從而破壞了它自己方法的實現,進而破壞了不可變的特性。設計
咱們都不是Java語言的設計者,不知道其爲什麼必定要設計成不可變,試着作一些猜測。
這是結合我的理解和stackoverflow上看的彙總,咱們來看看Java語言的爸爸James Gosling是怎麼說的。
From a strategic point of view, they tend to more often be trouble free. And there are usually things you can do with immutables that you can't do with mutable things, such as cache the result. If you pass a string to a file open method, or if you pass a string to a constructor for a label in a user interface, in some APIs (like in lots of the Windows APIs) you pass in an array of characters. The receiver of that object really has to copy it, because they don't know anything about the storage lifetime of it. And they don't know what's happening to the object, whether it is being changed under their feet.
You end up getting almost forced to replicate the object because you don't know whether or not you get to own it. And one of the nice things about immutable objects is that the answer is, "Yeah, of course you do." Because the question of ownership, who has the right to change it, doesn't exist.
One of the things that forced Strings to be immutable was security. You have a file open method. You pass a String to it. And then it's doing all kind of authentication checks before it gets around to doing the OS call. If you manage to do something that effectively mutated the String, after the security check and before the OS call, then boom, you're in. But Strings are immutable, so that kind of attack doesn't work. That precise example is what really demanded that Strings be immutable.
這是James Gosling在2001年5月的一次訪談中,談到了不可變類和String,大意就是 他會更傾向於使用不可變類,它可以緩存結果,當你在傳參的時候,使用不可變類不須要去考慮誰可能會修改其內部的值,這個問題不存在的。若是使用可變類的話,可能須要每次記得從新拷貝出裏面的值,性能會有必定的損失。
老爺子還說了,迫使String類設計成不可變的另外一個緣由是安全,當你在調用其餘方法,好比調用一些系統級操做以前,可能會有一系列校驗,若是是可變類的話,可能在你校驗事後,其內部的值被改變了,可能引發嚴重的系統崩潰問題,這是迫使String類設計成不可變類的重要緣由。
上文說了,設計成不可變後,能夠多個變量引用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 = new String("abc");
答案是不會,使用new關鍵字會在堆區在建立出一個字符串,因此使用new來建立字符串仍是很浪費內存的,內存結構以下圖所示。

首先咱們來看這一段代碼,應該是以前寫代碼比較常見的。
String test1 = "abc"; String test2 = "abc"; String test3 = test1 + test2;
test3經過test1和test2拼接而成,咱們看一下這個過程當中的字節碼。

從以上圖咱們能夠看到,目前的JDK7的作法是,會經過新建StringBuilder的方式來完成這個+號的操做。這是目前的一個底層字節碼的實現,那麼是否是沒有使用StringBuilder或者StringBuffer的必要了呢。仍是有的,看下一個例子。
String test2 = "abc"; String test3 = "abc"; for (int i = 0; i < 5; i++) { test3 += test2; }
在上述代碼中,咱們仍是使用+號進行拼接,但此次咱們加了一個循環,看一下字節碼有什麼變化。

每次循環都會建立一個StringBuilder,在末尾再調用toString返還回去,效率很低。繼續看下一個例子,咱們直接使用StringBuilder,來作拼接。
String test2 = "abc"; // 使用StringBuilder進行拼接 StringBuilder test4 = new StringBuilder("abc"); for (int i = 0; i < 5; i++) { test4.append(test2); }

每次循環體中只會調用以前建立的StringBuilder的append方法進行拼接,效率大大提升。
至於StringBuilder 的內部實現,諸位有興趣能夠本身再去看一下,本質上也是一個char數組上的操做,和StringBuffer的區別在於,StringBuffer是有作同步處理的,而StringBuilder沒有。
本文主要探討了String類設計爲Final修飾和不可變類的緣由,以及爲什麼在平常工做中不推薦使用+號進行字符串拼接。