前些天面試遇到一個很是難的關於String的問題,「String爲什麼被設計爲不可變的」?相似的問題也有「String爲什麼被設計爲final?」我的認爲仍是前面一種問法更準確,設計成final僅僅保證了String類不能被繼承,而Immutable相對於final要嚴格的多。
html
下文主要翻譯自:http://java67.blogspot.sg/2014/01/why-string-class-has-made-immutable-or-final-java.htmljava
要回答這個問題,java程序員必須對String是如何工做的,它的特性是什麼,還有一些關鍵原則有一個很深入的理解。String類在Java裏是一個上帝類,它有一些其餘類所不具有的特性,好比String字面量存儲在常量池,你能夠經過操做符「+」來鏈接多個String。鑑於String類在Java編程裏的重要性,Java設計者把它設計爲final的,這意味着你不能夠繼承這個類,一樣這也有助於String對象的不可變。
記得在某個地方讀到過,有人向Java的建立者James Gosling問過爲何要把String類設計成final,但他在安全性方面做了一些回答。有人認爲,使類設計成final嚴重限制了它的進化或擴展能力,James評論說,把類設計成final是Java安全性承諾的關鍵因素,這樣在Java平臺裏就沒有人能夠改變它的行爲了。
如今回到標題的問題,Java裏String爲何是不可變的呢?首先能夠肯定的是這麼設計是有優點的。如今,讓咱們思考一下這些優勢或特性,這是決定這樣設計的緣由。
下面列出5個Java裏把String設計爲final或者Immutable的緣由:
除了JamesGosling關於安全性的提示以外,我認爲如下緣由也說明了爲何String在Java中設計成final或Immutable的
1)String常量池
java設計者明白String類將會是全部Java應用中使用最多的類,這也是他們想從設計之初就要優化的緣由。優化方向的一個關鍵想法是在String常量池中存儲String字面量。目標是經過共享來減小臨時字符串對象,爲了共享,String類必須是不可變的。你不能在一個彼此不互知的雙方之間共享可變對象。讓咱們以一個假設的例子爲例,其中兩個引用變量指向同一個字符串對象:
String s1 = "Java";
String s2 = "Java";
如今假如s1的值被改爲「C++」,引用變量s2在它不知道的狀況下,他的值也變成了「C++」。可是經過把String設計爲不可變,上述的共享String字面量成爲了可能。總之,要在Java中實現字符串池的關鍵思想,必須把String類設計成不可變的。
2) 安全性
Java在爲每一個級別的服務提供安全環境方面有着明確的目標,而字符串在整個安全方面是相當重要的。 String已被普遍用做許多Java類的參數,例如,打開網絡鏈接時,能夠將主機和端口做爲字符串傳遞,在Java中讀取文件時,能夠將文件和目錄的路徑做爲字符串傳遞,打開數據庫鏈接時,能夠將數據庫URL做爲字符串傳遞。若是字符串不是不可變的,用戶可能已經受權訪問系統中的特定文件,可是在身份驗證以後,他能夠更改到其餘文件的路徑,這可能會致使嚴重的安全問題。相似地,在鏈接到數據庫或網絡中的任何其餘機器時,可變字符串值可能會形成安全威脅。可變字符串也可能致使反射中的安全性問題,由於參數是字符串。
3) 字符串在類加載機制中的應用
另外一個使字符串成爲最終或不可變的緣由是由於它在類加載機制中被大量使用。因爲字符串不是不可變的,攻擊者能夠利用這一事實,將加載標準Java類(例如java.io.Reader)的請求更改成惡意類com.un未知n.DataStolenReader。經過保持字符串的最終性和不可變性,咱們至少能夠確保JVM正在加載正確的類。
4) 多線程好處
因爲併發性和多線程是Java的關鍵產品,所以考慮字符串對象的線程安全性是頗有意義的。因爲預期字符串將被普遍使用,使其不可變意味着沒有額外的同步,所以意味着在多個線程之間共享字符串的代碼要簡單得多。這個單一的特性使得已經很複雜、混亂和容易出錯的併發編碼變得更加容易。由於字符串是不可變的,並且咱們只是在線程之間共享它,因此它會產生更易讀的代碼。
5) 優化與性能
如今,當你使類不可變時,您預先知道,這個類一旦建立就不會改變。這保證了許多性能優化的開放思惟,例如緩存。字符串自己知道,我不會更改,因此字符串緩存它的hashcode。它甚至延遲計算hashcode,一旦建立,只需緩存它。在簡單的世界中,當您第一次調用任何String對象的hashCode()方法時,它計算哈希代碼,而後對hashCode()的全部後續調用都返回已經計算的緩存值。這將帶來良好的性能增益,由於字符串在基於哈希的映射(例如Hashtable和HashMap)中大量使用。若是不使其不可變,就不可能緩存哈希代碼,由於它取決於字符串自己的內容。
除了上述優勢外,您還能夠考慮到在Java中字符串是immutable的優勢。它是最流行的對象之一,可用做基於哈希集合的鍵,例如HashMap和Hashtable。雖然對HashMap鍵來講,不可變性不是絕對須要的,但使用不可變對象做爲鍵要比使用可變對象安全得多,由於若是可變對象的狀態在HashMap中停留期間發生了更改,那麼就不可能檢索回它,由於它的Eques()和hashCode()方法依賴於已更改的屬性。若是一個類是不可變的,那麼當它存儲在基於哈希的集合中時,就沒有改變它的狀態的風險,我已經強調的另外一個重要的好處是它的線程安全性。因爲字符串是不可變的,因此您能夠在線程之間安全地共享它,而沒必要擔憂額外同步。它使併發代碼更具可讀性,減小了錯誤的發生。
儘管有全部這些優勢,但不變性也有一些缺點,例如,它並不是沒有成本。因爲字符串是不可變的,所以它會生成大量臨時對象和逃逸對象,這會給垃圾收集器帶來壓力。Java設計者已經考慮過了,將字符串字面量存儲在常量池中是他們減小字符串垃圾的解決方案。這確實有幫助,可是你必須當心地建立字符串而不使用構造函數,例如,new String()不會從字符串池中選擇對象。並且,平均來講,Java應用程序產生的垃圾太多了。此外,將字符串存儲在池中有與其相關的隱藏風險。字符串池位於JavaHeap的PermGen空間中,與JavaHeap相比,這是很是有限的。擁有太多的字符串文字會很快佔滿這個空間,從而導java.lang.OutOfMemoryError。值得慶幸的是,Java語言設計者已經意識到了這個問題,從Java 7開始,他們將字符串池移動到正常的堆空間,這比PermGen空間要大得多。使字符串成爲不可變的還有另外一個缺點,由於它限制了它的可擴展性。如今,您不能擴展String來提供更多的功能,雖然不多須要。