標籤:解決方案 解決 memory 思想 收集 進化 類載入機制 出錯 lang
前些天面試遇到一個非常難的關於String的問題,“String為何被設計為不可變的”?類似的問題也有“String為何被設計為final?”個人認為還是前面一種問法更準確,設計成final僅僅保證了String類不能被繼承,而Immutable相對於final要嚴格的多。
下文主要翻譯自:http://java67.blogspot.sg/2014/01/why-string-class-has-made-immutable-or-final-java.html
要回答這個問題,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) 最佳化與效能 現在,當你使類不可變時,您預Crowdsourced Security Testing道,這個類一旦建立就不會改變。這保證了許多效能最佳化的開放思維,例如緩衝。字串本身知道,我不會更改,所以字串緩衝它的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來提供更多的功能,雖然很少需要。
java裡String類為何被設計為final